From 930448b02bffc5ccfa636c9fdf1dcc72266bc398 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 16 Dec 2020 18:35:25 +0100 Subject: [PATCH] SOP: Add documentation and parse not-{before|after} dates in verify --- .../pgpainless/sop/commands/GenerateKey.java | 4 +- .../org/pgpainless/sop/commands/Verify.java | 114 +++++++++++++----- .../org/pgpainless/sop/commands/Version.java | 2 +- 3 files changed, 87 insertions(+), 33 deletions(-) diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/GenerateKey.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/GenerateKey.java index 225cca9c..745411d7 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/GenerateKey.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/GenerateKey.java @@ -17,10 +17,10 @@ public class GenerateKey implements Runnable { @CommandLine.Option(names = {"--armor"}, description = "ASCII Armor the output") boolean armor = false; - @CommandLine.Option(names = {"--no-armor"}) + @CommandLine.Option(names = {"--no-armor"}, description = "Non-armored, binary output") boolean noArmor = false; - @CommandLine.Parameters + @CommandLine.Parameters(description = "User-ID, eg. \"Alice \"") String userId; @Override diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Verify.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Verify.java index 66fef9e1..9cb08135 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Verify.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Verify.java @@ -15,39 +15,48 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.text.DateFormat; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; -@CommandLine.Command(name = "verify") +@CommandLine.Command(name = "verify", description = "Verify a detached signature.\nThe signed data is being read from standard input.") public class Verify implements Runnable { - @CommandLine.Parameters(index = "0", description = "The detached signature") + @CommandLine.Parameters(index = "0", description = "Detached signature") File signature; - @CommandLine.Parameters(index = "1..*") - File[] certs; + @CommandLine.Parameters(index = "1..*", arity = "1..*", description = "Public key certificates") + File[] certificates; - TimeZone tz = TimeZone.getTimeZone("UTC"); - DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'"); + @CommandLine.Option(names = {"--not-before"}, description = "ISO-8601 formatted UTC date (eg. '2020-11-23T16:35Z)\n" + + "Reject signatures with a creation date not in range.\n" + + "Defaults to beginning of time (\"-\").") + String notBefore = "-"; + + @CommandLine.Option(names = {"--not-after"}, description = "ISO-8601 formatted UTC date (eg. '2020-11-23T16:35Z)\n" + + "Reject signatures with a creation date not in range.\n" + + "Defaults to current system time (\"now\").\n" + + "Accepts special value \"-\" for end of time.") + String notAfter = "now"; + + private final TimeZone tz = TimeZone.getTimeZone("UTC"); + private final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'"); + + private final Date beginningOfTime = new Date(0); + private final Date endOfTime = new Date(8640000000000000L); @Override public void run() { df.setTimeZone(tz); + Date notBeforeDate = parseNotBefore(); + Date notAfterDate = parseNotAfter(); - if (certs.length == 0) { + Map publicKeys = readCertificatesFromFiles(); + if (publicKeys.isEmpty()) { System.out.println("No certificates supplied."); System.exit(19); } - Map publicKeys = new HashMap<>(); - for (File cert : certs) { - try(FileInputStream in = new FileInputStream(cert)) { - publicKeys.put(cert, PGPainless.readKeyRing().publicKeyRing(in)); - } catch (IOException e) { - e.printStackTrace(); - } - } - try(FileInputStream sigIn = new FileInputStream(signature)) { DecryptionStream verifier = PGPainless.decryptAndOrVerify() .onInputStream(System.in) @@ -63,28 +72,73 @@ public class Verify implements Runnable { OpenPgpMetadata metadata = verifier.getResult(); - for (OpenPgpV4Fingerprint sigKeyFp : metadata.getVerifiedSignatures().keySet()) { - PGPSignature signature = metadata.getVerifiedSignatures().get(sigKeyFp); - for (File file : certs) { - // Search signing key ring - PGPPublicKeyRing publicKeyRing = publicKeys.get(file); - if (publicKeyRing.getPublicKey(sigKeyFp.getKeyId()) == null) { - continue; - } - - String utcSigDate = df.format(signature.getCreationTime()); - OpenPgpV4Fingerprint primaryKeyFp = new OpenPgpV4Fingerprint(publicKeyRing); - System.out.println(utcSigDate + " " + sigKeyFp.toString() + " " + primaryKeyFp.toString() + - " signed by " + file.getName()); + Map signaturesInTimeRange = new HashMap<>(); + for (OpenPgpV4Fingerprint fingerprint : metadata.getVerifiedSignatures().keySet()) { + PGPSignature signature = metadata.getVerifiedSignatures().get(fingerprint); + Date creationTime = signature.getCreationTime(); + if (!creationTime.before(notBeforeDate) && !creationTime.after(notAfterDate)) { + signaturesInTimeRange.put(fingerprint, signature); } } - if (metadata.getVerifiedSignatures().isEmpty()) { + if (signaturesInTimeRange.isEmpty()) { System.out.println("Signature validation failed."); System.exit(3); } + + printValidSignatures(signaturesInTimeRange, publicKeys); } catch (IOException | PGPException e) { e.printStackTrace(); } } + + private void printValidSignatures(Map validSignatures, Map publicKeys) { + for (OpenPgpV4Fingerprint sigKeyFp : validSignatures.keySet()) { + PGPSignature signature = validSignatures.get(sigKeyFp); + for (File file : publicKeys.keySet()) { + // Search signing key ring + PGPPublicKeyRing publicKeyRing = publicKeys.get(file); + if (publicKeyRing.getPublicKey(sigKeyFp.getKeyId()) == null) { + continue; + } + + String utcSigDate = df.format(signature.getCreationTime()); + OpenPgpV4Fingerprint primaryKeyFp = new OpenPgpV4Fingerprint(publicKeyRing); + System.out.println(utcSigDate + " " + sigKeyFp.toString() + " " + primaryKeyFp.toString() + + " signed by " + file.getName()); + } + } + } + + private Map readCertificatesFromFiles() { + Map publicKeys = new HashMap<>(); + for (File cert : certificates) { + try(FileInputStream in = new FileInputStream(cert)) { + publicKeys.put(cert, PGPainless.readKeyRing().publicKeyRing(in)); + } catch (IOException e) { + e.printStackTrace(); + } + } + return publicKeys; + } + + private Date parseNotAfter() { + try { + return notAfter.equals("now") ? new Date() : notAfter.equals("-") ? endOfTime : df.parse(notAfter); + } catch (ParseException e) { + System.out.println("Invalid date string supplied as value of --not-after."); + System.exit(1); + return null; + } + } + + private Date parseNotBefore() { + try { + return notBefore.equals("now") ? new Date() : notBefore.equals("-") ? beginningOfTime: df.parse(notBefore); + } catch (ParseException e) { + System.out.println("Invalid date string supplied as value of --not-before."); + System.exit(1); + return null; + } + } } diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Version.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Version.java index 25a646b2..772dbf88 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Version.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Version.java @@ -2,7 +2,7 @@ package org.pgpainless.sop.commands; import picocli.CommandLine; -@CommandLine.Command(name = "version") +@CommandLine.Command(name = "version", description = "Display version information about the tool") public class Version implements Runnable { @Override