1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-11-16 01:12:05 +01:00

Fix various checkstyle issues

This commit is contained in:
Paul Schaub 2020-12-16 20:09:01 +01:00
parent ffd46b6d5e
commit 31cfbaa4b2
11 changed files with 350 additions and 74 deletions

View file

@ -1,6 +1,27 @@
/*
* Copyright 2020 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.sop; package org.pgpainless.sop;
import org.pgpainless.sop.commands.*; import org.pgpainless.sop.commands.Armor;
import org.pgpainless.sop.commands.Dearmor;
import org.pgpainless.sop.commands.ExtractCert;
import org.pgpainless.sop.commands.GenerateKey;
import org.pgpainless.sop.commands.Sign;
import org.pgpainless.sop.commands.Verify;
import org.pgpainless.sop.commands.Version;
import picocli.CommandLine; import picocli.CommandLine;
@CommandLine.Command( @CommandLine.Command(
@ -9,36 +30,17 @@ import picocli.CommandLine;
GenerateKey.class, GenerateKey.class,
ExtractCert.class, ExtractCert.class,
Sign.class, Sign.class,
Verify.class Verify.class,
Armor.class,
Dearmor.class
} }
) )
public class PGPainlessCLI implements Runnable { public class PGPainlessCLI implements Runnable {
public static void main(String[] args) { public static void main(String[] args) {
interpret(args);
// generateKey();
}
public static void interpret(String... args) {
CommandLine.run(new PGPainlessCLI(), args); CommandLine.run(new PGPainlessCLI(), args);
} }
private static void version() {
CommandLine.run(new PGPainlessCLI(), "version");
}
private static void generateKey() {
interpret("generate-key", "--armor", "Alice Example <alice@wonderland.lit>");
}
private static void extractCert() {
CommandLine.run(new PGPainlessCLI(), "extract-cert");
}
private static void sign() {
interpret("sign", "--armor", "--as=text", "alice.sec");
}
@Override @Override
public void run() { public void run() {

View file

@ -1,3 +1,18 @@
/*
* Copyright 2020 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.sop; package org.pgpainless.sop;
import java.io.IOException; import java.io.IOException;
@ -13,4 +28,16 @@ public class Print {
return new String(bytes, "UTF-8"); return new String(bytes, "UTF-8");
} }
} }
public static void print_ln(String msg) {
// CHECKSTYLE:OFF
System.out.println(msg);
// CHECKSTYLE:ON
}
public static void err_ln(String msg) {
// CHECKSTYLE:OFF
System.err.println(msg);
// CHECKSTYLE:ON
}
} }

View file

@ -0,0 +1,66 @@
/*
* Copyright 2020 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.sop.commands;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.util.io.Streams;
import picocli.CommandLine;
import java.io.IOException;
import java.io.PushbackInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static org.pgpainless.sop.Print.err_ln;
@CommandLine.Command(name = "armor", description = "Add ASCII Armor")
public class Armor implements Runnable {
private static final byte[] BEGIN_ARMOR = "-----BEGIN PGP".getBytes(StandardCharsets.UTF_8);
private enum Label {
auto,
sig,
key,
cert,
message
}
@CommandLine.Option(names = {"--label"}, description = "Label to be used in the header and tail of the armoring.", paramLabel = "{auto|sig|key|cert|message}")
Label label;
@CommandLine.Option(names = {"--allow-nested"}, description = "Allow additional armoring of already armored input")
boolean allowNested = false;
@Override
public void run() {
try (PushbackInputStream pbIn = new PushbackInputStream(System.in); ArmoredOutputStream armoredOutputStream = new ArmoredOutputStream(System.out)) {
byte[] start = new byte[14];
int read = pbIn.read(start);
pbIn.unread(read);
if (Arrays.equals(BEGIN_ARMOR, start) && !allowNested) {
Streams.pipeAll(pbIn, System.out);
} else {
Streams.pipeAll(pbIn, armoredOutputStream);
}
} catch (IOException e) {
err_ln("Input data cannot be ASCII armored.");
err_ln(e.getMessage());
System.exit(1);
}
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright 2020 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.sop.commands;
import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.util.io.Streams;
import picocli.CommandLine;
import java.io.IOException;
import static org.pgpainless.sop.Print.err_ln;
@CommandLine.Command(name = "dearmor", description = "Remove ASCII Armor")
public class Dearmor implements Runnable {
@Override
public void run() {
try (ArmoredInputStream in = new ArmoredInputStream(System.in, true)) {
Streams.pipeAll(in, System.out);
} catch (IOException e) {
err_ln("Data cannot be dearmored.");
err_ln(e.getMessage());
System.exit(1);
}
}
}

View file

@ -1,3 +1,18 @@
/*
* Copyright 2020 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.sop.commands; package org.pgpainless.sop.commands;
import java.io.IOException; import java.io.IOException;
@ -10,6 +25,9 @@ import org.pgpainless.sop.Print;
import org.pgpainless.util.BCUtil; import org.pgpainless.util.BCUtil;
import picocli.CommandLine; import picocli.CommandLine;
import static org.pgpainless.sop.Print.err_ln;
import static org.pgpainless.sop.Print.print_ln;
@CommandLine.Command(name = "extract-cert") @CommandLine.Command(name = "extract-cert")
public class ExtractCert implements Runnable { public class ExtractCert implements Runnable {
@ -25,10 +43,10 @@ public class ExtractCert implements Runnable {
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(System.in); PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(System.in);
PGPPublicKeyRing publicKeys = BCUtil.publicKeyRingFromSecretKeyRing(secretKeys); PGPPublicKeyRing publicKeys = BCUtil.publicKeyRingFromSecretKeyRing(secretKeys);
System.out.println(Print.toString(publicKeys.getEncoded(), !noArmor)); print_ln(Print.toString(publicKeys.getEncoded(), !noArmor));
} catch (IOException | PGPException e) { } catch (IOException | PGPException e) {
System.err.println("Error extracting certificate from keys;"); err_ln("Error extracting certificate from keys;");
System.err.println(e.getMessage()); err_ln(e.getMessage());
System.exit(1); System.exit(1);
} }
} }

View file

@ -1,3 +1,18 @@
/*
* Copyright 2020 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.sop.commands; package org.pgpainless.sop.commands;
import java.io.IOException; import java.io.IOException;
@ -8,9 +23,11 @@ import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.sop.Print; import org.pgpainless.sop.Print;
import org.pgpainless.util.ArmorUtils;
import picocli.CommandLine; import picocli.CommandLine;
import static org.pgpainless.sop.Print.err_ln;
import static org.pgpainless.sop.Print.print_ln;
@CommandLine.Command(name = "generate-key") @CommandLine.Command(name = "generate-key")
public class GenerateKey implements Runnable { public class GenerateKey implements Runnable {
@ -28,11 +45,11 @@ public class GenerateKey implements Runnable {
try { try {
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().simpleEcKeyRing(userId); PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().simpleEcKeyRing(userId);
System.out.println(Print.toString(secretKeys.getEncoded(), !noArmor)); print_ln(Print.toString(secretKeys.getEncoded(), !noArmor));
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | PGPException | IOException e) { } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | PGPException | IOException e) {
System.err.println("Error creating OpenPGP key:"); err_ln("Error creating OpenPGP key:");
System.err.println(e.getMessage()); err_ln(e.getMessage());
System.exit(1); System.exit(1);
} }
} }

View file

@ -1,3 +1,18 @@
/*
* Copyright 2020 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.sop.commands; package org.pgpainless.sop.commands;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -16,6 +31,9 @@ import org.pgpainless.key.protection.UnprotectedKeysProtector;
import org.pgpainless.sop.Print; import org.pgpainless.sop.Print;
import picocli.CommandLine; import picocli.CommandLine;
import static org.pgpainless.sop.Print.err_ln;
import static org.pgpainless.sop.Print.print_ln;
@CommandLine.Command(name = "sign") @CommandLine.Command(name = "sign")
public class Sign implements Runnable { public class Sign implements Runnable {
@ -30,7 +48,8 @@ public class Sign implements Runnable {
@CommandLine.Option(names = {"--no-armor"}) @CommandLine.Option(names = {"--no-armor"})
boolean noArmor = false; boolean noArmor = false;
@CommandLine.Option(names = "--as", description = "Defaults to 'binary'. If '--as=text' and the input data is not valid UTF-8, sign fails with return code 53.") @CommandLine.Option(names = "--as", description = "Defaults to 'binary'. If '--as=text' and the input data is not valid UTF-8, sign fails with return code 53.",
paramLabel = "{binary|text}")
Type type; Type type;
@CommandLine.Parameters @CommandLine.Parameters
@ -42,9 +61,8 @@ public class Sign implements Runnable {
try { try {
secretKeys = PGPainless.readKeyRing().secretKeyRing(new FileInputStream(secretKeyFile)); secretKeys = PGPainless.readKeyRing().secretKeyRing(new FileInputStream(secretKeyFile));
} catch (IOException | PGPException e) { } catch (IOException | PGPException e) {
System.err.println("Error reading secret key ring."); err_ln("Error reading secret key ring.");
System.err.println(e.getMessage()); err_ln(e.getMessage());
System.exit(1); System.exit(1);
return; return;
} }
@ -64,11 +82,10 @@ public class Sign implements Runnable {
PGPSignature signature = encryptionStream.getResult().getSignatures().iterator().next(); PGPSignature signature = encryptionStream.getResult().getSignatures().iterator().next();
System.out.println(Print.toString(signature.getEncoded(), !noArmor)); print_ln(Print.toString(signature.getEncoded(), !noArmor));
} catch (PGPException | IOException e) { } catch (PGPException | IOException e) {
System.err.println("Error signing data."); err_ln("Error signing data.");
System.err.println(e.getMessage()); err_ln(e.getMessage());
System.exit(1); System.exit(1);
} }
} }

View file

@ -1,3 +1,18 @@
/*
* Copyright 2020 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.sop.commands; package org.pgpainless.sop.commands;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPException;
@ -12,6 +27,7 @@ import picocli.CommandLine;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.text.DateFormat; import java.text.DateFormat;
@ -23,9 +39,22 @@ import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import static org.pgpainless.sop.Print.err_ln;
import static org.pgpainless.sop.Print.print_ln;
@CommandLine.Command(name = "verify", description = "Verify a detached signature.\nThe signed data is being read from standard input.") @CommandLine.Command(name = "verify", description = "Verify a detached signature.\nThe signed data is being read from standard input.")
public class Verify implements Runnable { public class Verify implements Runnable {
private static final TimeZone tz = TimeZone.getTimeZone("UTC");
private static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
private static final Date beginningOfTime = new Date(0);
private static final Date endOfTime = new Date(8640000000000000L);
static {
df.setTimeZone(tz);
}
@CommandLine.Parameters(index = "0", description = "Detached signature") @CommandLine.Parameters(index = "0", description = "Detached signature")
File signature; File signature;
@ -43,24 +72,18 @@ public class Verify implements Runnable {
"Accepts special value \"-\" for end of time.") "Accepts special value \"-\" for end of time.")
String notAfter = "now"; 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 @Override
public void run() { public void run() {
df.setTimeZone(tz);
Date notBeforeDate = parseNotBefore(); Date notBeforeDate = parseNotBefore();
Date notAfterDate = parseNotAfter(); Date notAfterDate = parseNotAfter();
Map<File, PGPPublicKeyRing> publicKeys = readCertificatesFromFiles(); Map<File, PGPPublicKeyRing> publicKeys = readCertificatesFromFiles();
if (publicKeys.isEmpty()) { if (publicKeys.isEmpty()) {
System.out.println("No certificates supplied."); err_ln("No certificates supplied.");
System.exit(19); System.exit(19);
} }
OpenPgpMetadata metadata;
try (FileInputStream sigIn = new FileInputStream(signature)) { try (FileInputStream sigIn = new FileInputStream(signature)) {
DecryptionStream verifier = PGPainless.decryptAndOrVerify() DecryptionStream verifier = PGPainless.decryptAndOrVerify()
.onInputStream(System.in) .onInputStream(System.in)
@ -74,7 +97,18 @@ public class Verify implements Runnable {
Streams.pipeAll(verifier, out); Streams.pipeAll(verifier, out);
verifier.close(); verifier.close();
OpenPgpMetadata metadata = verifier.getResult(); metadata = verifier.getResult();
} catch (FileNotFoundException e) {
err_ln("Signature file not found:");
err_ln(e.getMessage());
System.exit(1);
return;
} catch (IOException | PGPException e) {
err_ln("Signature validation failed.");
err_ln(e.getMessage());
System.exit(1);
return;
}
Map<OpenPgpV4Fingerprint, PGPSignature> signaturesInTimeRange = new HashMap<>(); Map<OpenPgpV4Fingerprint, PGPSignature> signaturesInTimeRange = new HashMap<>();
for (OpenPgpV4Fingerprint fingerprint : metadata.getVerifiedSignatures().keySet()) { for (OpenPgpV4Fingerprint fingerprint : metadata.getVerifiedSignatures().keySet()) {
@ -86,14 +120,11 @@ public class Verify implements Runnable {
} }
if (signaturesInTimeRange.isEmpty()) { if (signaturesInTimeRange.isEmpty()) {
System.out.println("Signature validation failed."); err_ln("No valid signatures found.");
System.exit(3); System.exit(3);
} }
printValidSignatures(signaturesInTimeRange, publicKeys); printValidSignatures(signaturesInTimeRange, publicKeys);
} catch (IOException | PGPException e) {
e.printStackTrace();
}
} }
private void printValidSignatures(Map<OpenPgpV4Fingerprint, PGPSignature> validSignatures, Map<File, PGPPublicKeyRing> publicKeys) { private void printValidSignatures(Map<OpenPgpV4Fingerprint, PGPSignature> validSignatures, Map<File, PGPPublicKeyRing> publicKeys) {
@ -108,7 +139,7 @@ public class Verify implements Runnable {
String utcSigDate = df.format(signature.getCreationTime()); String utcSigDate = df.format(signature.getCreationTime());
OpenPgpV4Fingerprint primaryKeyFp = new OpenPgpV4Fingerprint(publicKeyRing); OpenPgpV4Fingerprint primaryKeyFp = new OpenPgpV4Fingerprint(publicKeyRing);
System.out.println(utcSigDate + " " + sigKeyFp.toString() + " " + primaryKeyFp.toString() + print_ln(utcSigDate + " " + sigKeyFp.toString() + " " + primaryKeyFp.toString() +
" signed by " + file.getName()); " signed by " + file.getName());
} }
} }
@ -120,7 +151,8 @@ public class Verify implements Runnable {
try (FileInputStream in = new FileInputStream(cert)) { try (FileInputStream in = new FileInputStream(cert)) {
publicKeys.put(cert, PGPainless.readKeyRing().publicKeyRing(in)); publicKeys.put(cert, PGPainless.readKeyRing().publicKeyRing(in));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); err_ln("Cannot read certificate from file " + cert.getAbsolutePath() + ":");
err_ln(e.getMessage());
} }
} }
return publicKeys; return publicKeys;
@ -130,7 +162,7 @@ public class Verify implements Runnable {
try { try {
return notAfter.equals("now") ? new Date() : notAfter.equals("-") ? endOfTime : df.parse(notAfter); return notAfter.equals("now") ? new Date() : notAfter.equals("-") ? endOfTime : df.parse(notAfter);
} catch (ParseException e) { } catch (ParseException e) {
System.out.println("Invalid date string supplied as value of --not-after."); err_ln("Invalid date string supplied as value of --not-after.");
System.exit(1); System.exit(1);
return null; return null;
} }
@ -140,7 +172,7 @@ public class Verify implements Runnable {
try { try {
return notBefore.equals("now") ? new Date() : notBefore.equals("-") ? beginningOfTime : df.parse(notBefore); return notBefore.equals("now") ? new Date() : notBefore.equals("-") ? beginningOfTime : df.parse(notBefore);
} catch (ParseException e) { } catch (ParseException e) {
System.out.println("Invalid date string supplied as value of --not-before."); err_ln("Invalid date string supplied as value of --not-before.");
System.exit(1); System.exit(1);
return null; return null;
} }

View file

@ -1,12 +1,29 @@
/*
* Copyright 2020 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.sop.commands; package org.pgpainless.sop.commands;
import picocli.CommandLine; import picocli.CommandLine;
import static org.pgpainless.sop.Print.print_ln;
@CommandLine.Command(name = "version", description = "Display version information about the tool") @CommandLine.Command(name = "version", description = "Display version information about the tool")
public class Version implements Runnable { public class Version implements Runnable {
@Override @Override
public void run() { public void run() {
System.out.println("PGPainless CLI version 0.0.1"); print_ln("PGPainless CLI version 0.0.1");
} }
} }

View file

@ -0,0 +1,19 @@
/*
* Copyright 2020 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Subcommands of the PGPainless SOP.
*/
package org.pgpainless.sop.commands;

View file

@ -0,0 +1,22 @@
/*
* Copyright 2020 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* PGPainless SOP implementing a Stateless OpenPGP Command Line Interface.
* @see <a href="https://tools.ietf.org/html/draft-dkg-openpgp-stateless-cli-01">
* Stateless OpenPGP Command Line Interface
* draft-dkg-openpgp-stateless-cli-01</a>
*/
package org.pgpainless.sop;