diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index bc5eb46..189b648 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -32,6 +32,7 @@ import sop.operation.InlineDetach; import sop.operation.InlineSign; import sop.operation.InlineVerify; import sop.operation.ListProfiles; +import sop.operation.RevokeKey; import sop.operation.Version; import javax.annotation.Nonnull; @@ -161,6 +162,11 @@ public class ExternalSOP implements SOP { return new ListProfilesExternal(binaryName, properties); } + @Override + public RevokeKey revokeKey() { + return null; + } + @Override public Dearmor dearmor() { return new DearmorExternal(binaryName, properties); diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java index fab99e0..2f98061 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java @@ -17,6 +17,7 @@ import sop.cli.picocli.commands.GenerateKeyCmd; import sop.cli.picocli.commands.InlineSignCmd; import sop.cli.picocli.commands.InlineVerifyCmd; import sop.cli.picocli.commands.ListProfilesCmd; +import sop.cli.picocli.commands.RevokeKeyCmd; import sop.cli.picocli.commands.SignCmd; import sop.cli.picocli.commands.VerifyCmd; import sop.cli.picocli.commands.VersionCmd; @@ -43,6 +44,7 @@ import java.util.ResourceBundle; InlineSignCmd.class, InlineVerifyCmd.class, ListProfilesCmd.class, + RevokeKeyCmd.class, VersionCmd.class, AutoComplete.GenerateCompletion.class } diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/RevokeKeyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/RevokeKeyCmd.java new file mode 100644 index 0000000..28d1890 --- /dev/null +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/RevokeKeyCmd.java @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands; + +import picocli.CommandLine; +import sop.Ready; +import sop.cli.picocli.SopCLI; +import sop.exception.SOPGPException; +import sop.operation.RevokeKey; + +import java.io.IOException; + +@CommandLine.Command(name = "revoke-key", + resourceBundle = "msg_revoke-key", + exitCodeOnInvalidInput = 37) +public class RevokeKeyCmd extends AbstractSopCmd { + + @CommandLine.Option(names = "--no-armor", + negatable = true) + boolean armor = true; + + @CommandLine.Option(names = "--with-key-password", + paramLabel = "PASSWORD") + String withKeyPassword; + + @Override + public void run() { + RevokeKey revokeKey = throwIfUnsupportedSubcommand( + SopCLI.getSop().revokeKey(), "revoke-key"); + + if (!armor) { + revokeKey.noArmor(); + } + + if (withKeyPassword != null) { + try { + String password = stringFromInputStream(getInput(withKeyPassword)); + revokeKey.withKeyPassword(password); + } catch (SOPGPException.UnsupportedOption e) { + String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--with-key-password"); + throw new SOPGPException.UnsupportedOption(errorMsg, e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + Ready ready; + try { + ready = revokeKey.keys(System.in); + } catch (SOPGPException.KeyIsProtected e) { + String errorMsg = getMsg("sop.error.runtime.cannot_unlock_key", "STANDARD_IN"); + throw new SOPGPException.KeyIsProtected(errorMsg, e); + } + try { + ready.writeTo(System.out); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/sop-java-picocli/src/main/resources/msg_revoke-key.properties b/sop-java-picocli/src/main/resources/msg_revoke-key.properties new file mode 100644 index 0000000..e90cd48 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_revoke-key.properties @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2023 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Generate revocation certificate for private keys +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties b/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties new file mode 100644 index 0000000..f073213 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2023 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Erzeuge Widerrufszertifikate für private Schlüssel +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java index c05440e..67a951c 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java @@ -27,6 +27,7 @@ import sop.operation.InlineVerify; import sop.operation.DetachedSign; import sop.operation.DetachedVerify; import sop.operation.ListProfiles; +import sop.operation.RevokeKey; import sop.operation.Version; public class SOPTest { @@ -101,6 +102,11 @@ public class SOPTest { return null; } + @Override + public RevokeKey revokeKey() { + return null; + } + @Override public InlineDetach inlineDetach() { return null; diff --git a/sop-java/src/main/java/sop/SOP.java b/sop-java/src/main/java/sop/SOP.java index 804cd28..460e6c1 100644 --- a/sop-java/src/main/java/sop/SOP.java +++ b/sop-java/src/main/java/sop/SOP.java @@ -16,6 +16,7 @@ import sop.operation.InlineVerify; import sop.operation.DetachedSign; import sop.operation.DetachedVerify; import sop.operation.ListProfiles; +import sop.operation.RevokeKey; import sop.operation.Version; /** @@ -158,4 +159,11 @@ public interface SOP { * @return builder instance */ ListProfiles listProfiles(); + + /** + * Revoke one or more secret keys. + * + * @return builder instance + */ + RevokeKey revokeKey(); } diff --git a/sop-java/src/main/java/sop/operation/RevokeKey.java b/sop-java/src/main/java/sop/operation/RevokeKey.java new file mode 100644 index 0000000..e2aa433 --- /dev/null +++ b/sop-java/src/main/java/sop/operation/RevokeKey.java @@ -0,0 +1,49 @@ +// 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.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; + + Ready keys(InputStream keys); +}