From 4c58775f17fc7938fef0a3583bbab410dc73e3ee Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 4 Sep 2025 13:16:57 +0200 Subject: [PATCH] Add WIP status command --- .../bouncycastle/sop/BouncyCastleSOP.java | 18 ++---- .../bouncycastle/sop/operation/BCStatus.java | 54 +++++++++++++++++ .../main/java/org/pgpainless/BCStatusCmd.java | 58 +++++++++++++++++++ .../main/java/org/pgpainless/BcSopCLI.java | 2 + 4 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 bc-sop-api/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCStatus.java create mode 100644 bc-sop-cli/src/main/java/org/pgpainless/BCStatusCmd.java diff --git a/bc-sop-api/src/main/java/org/pgpainless/bouncycastle/sop/BouncyCastleSOP.java b/bc-sop-api/src/main/java/org/pgpainless/bouncycastle/sop/BouncyCastleSOP.java index a103332..508c1b0 100644 --- a/bc-sop-api/src/main/java/org/pgpainless/bouncycastle/sop/BouncyCastleSOP.java +++ b/bc-sop-api/src/main/java/org/pgpainless/bouncycastle/sop/BouncyCastleSOP.java @@ -5,19 +5,7 @@ import org.bouncycastle.openpgp.api.OpenPGPApi; import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.pgpainless.bouncycastle.sop.operation.BCArmor; -import org.pgpainless.bouncycastle.sop.operation.BCDearmor; -import org.pgpainless.bouncycastle.sop.operation.BCDecrypt; -import org.pgpainless.bouncycastle.sop.operation.BCDetachedSign; -import org.pgpainless.bouncycastle.sop.operation.BCDetachedVerify; -import org.pgpainless.bouncycastle.sop.operation.BCEncrypt; -import org.pgpainless.bouncycastle.sop.operation.BCExtractCert; -import org.pgpainless.bouncycastle.sop.operation.BCGenerateKey; -import org.pgpainless.bouncycastle.sop.operation.BCInlineSign; -import org.pgpainless.bouncycastle.sop.operation.BCInlineVerify; -import org.pgpainless.bouncycastle.sop.operation.BCListProfiles; -import org.pgpainless.bouncycastle.sop.operation.BCMergeCerts; -import org.pgpainless.bouncycastle.sop.operation.BCVersion; +import org.pgpainless.bouncycastle.sop.operation.*; import sop.SOP; import sop.exception.SOPGPException; import sop.operation.Armor; @@ -166,4 +154,8 @@ public class BouncyCastleSOP implements SOP { public ValidateUserId validateUserId() { throw new SOPGPException.UnsupportedSubcommand("validate-userid is not implemented."); } + + public BCStatus status() { + return new BCStatus(api); + } } diff --git a/bc-sop-api/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCStatus.java b/bc-sop-api/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCStatus.java new file mode 100644 index 0000000..0007f8b --- /dev/null +++ b/bc-sop-api/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCStatus.java @@ -0,0 +1,54 @@ +package org.pgpainless.bouncycastle.sop.operation; + +import org.bouncycastle.openpgp.api.OpenPGPApi; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.jetbrains.annotations.NotNull; +import sop.Ready; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class BCStatus extends AbstractBCOperation { + + private boolean json; + private Date time = null; + + public BCStatus(OpenPGPApi api) { + super(api); + } + + public BCStatus json(boolean json) { + this.json = json; + return this; + } + + public BCStatus time(Date time) { + this.time = time; + return this; + } + + public Ready cert(InputStream in) { + return new Ready() { + @Override + public void writeTo(@NotNull OutputStream outputStream) throws IOException { + OpenPGPCertificate cert = api.readKeyOrCertificate().parseCertificateOrKey(in); + + OpenPGPCertificate.CertStatus status; + if (time == null) { + status = cert.getStatus(); + } else { + status = cert.getStatus(time); + } + + if (json) { + outputStream.write(status.toJsonString().getBytes(StandardCharsets.UTF_8)); + } else { + outputStream.write(status.toString().getBytes(StandardCharsets.UTF_8)); + } + } + }; + } +} diff --git a/bc-sop-cli/src/main/java/org/pgpainless/BCStatusCmd.java b/bc-sop-cli/src/main/java/org/pgpainless/BCStatusCmd.java new file mode 100644 index 0000000..9a90f9c --- /dev/null +++ b/bc-sop-cli/src/main/java/org/pgpainless/BCStatusCmd.java @@ -0,0 +1,58 @@ +package org.pgpainless; + +import org.pgpainless.bouncycastle.sop.BouncyCastleSOP; +import org.pgpainless.bouncycastle.sop.operation.BCStatus; +import picocli.CommandLine; +import sop.Ready; +import sop.cli.picocli.SopCLI; +import sop.cli.picocli.commands.AbstractSopCmd; +import sop.exception.SOPGPException; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; + +@CommandLine.Command(name = "status", + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) +public class BCStatusCmd extends AbstractSopCmd { + + @CommandLine.Parameters(arity = "0..1") + public String fileName; + + @CommandLine.Option(names = {"--json"}) + public boolean json = false; + + @CommandLine.Option(names = {"--time"}, + paramLabel = "UTC_TIME") + Date time = null; + + @Override + public void run() { + BCStatus status = throwIfUnsupportedSubcommand( + ((BouncyCastleSOP) SopCLI.getSop()).status(), "status"); + + InputStream in; + if (fileName != null) { + try { + in = getInput(fileName); + } catch (FileNotFoundException e) { + throw new SOPGPException.MissingInput("Could not find file " + fileName, e); + } catch (IOException e) { + throw new SOPGPException.BadData(e); + } + } else { + in = System.in; + } + + Ready ready = status.json(json) + .time(time) + .cert(in); + + try { + ready.writeTo(System.out); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/bc-sop-cli/src/main/java/org/pgpainless/BcSopCLI.java b/bc-sop-cli/src/main/java/org/pgpainless/BcSopCLI.java index e401990..aa0a02e 100644 --- a/bc-sop-cli/src/main/java/org/pgpainless/BcSopCLI.java +++ b/bc-sop-cli/src/main/java/org/pgpainless/BcSopCLI.java @@ -1,6 +1,7 @@ package org.pgpainless; import org.pgpainless.bouncycastle.sop.BouncyCastleSOP; +import org.pgpainless.bouncycastle.sop.operation.BCStatus; import picocli.CommandLine; import sop.cli.picocli.SOPExceptionExitCodeMapper; import sop.cli.picocli.SOPExecutionExceptionHandler; @@ -22,6 +23,7 @@ public class BcSopCLI { .parseArgs(args); CommandLine cmd = new CommandLine(SopCLI.class); + cmd.addSubcommand("status", new BCStatusCmd()); cmd.getSubcommands().get("generate-completion").getCommandSpec().usageMessage().hidden(true); cmd.setCommandName("bcsop"); cmd.setExecutionExceptionHandler(new SOPExecutionExceptionHandler());