mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-26 06:12:06 +01:00
Base PGPainlessCLI on new sop-java module
* Rename pgpainless-sop -> pgpainless-cli * Introduce sop-java (implementation-independent SOP API) * Introduce sop-java-picocli (CLI frontend for sop-java) * Introduce pgpainless-sop (implementation of sop-java using PGPainless) * Rework pgpainless-cli (plugs pgpainless-sop into sop-java-picocli)
This commit is contained in:
parent
2ba782c451
commit
8cf5347b52
112 changed files with 6146 additions and 1303 deletions
|
@ -27,7 +27,7 @@ allprojects {
|
|||
apply plugin: 'checkstyle'
|
||||
|
||||
// For non-sop modules, enable android api compatibility check
|
||||
if (!it.name.equals('pgpainless-sop')) {
|
||||
if (it.name.equals('pgpainless-core') || it.name.equals('sop-java') || it.name.equals('pgpainless-sop')) {
|
||||
// animalsniffer
|
||||
apply plugin: 'ru.vyarus.animalsniffer'
|
||||
dependencies {
|
||||
|
|
66
pgpainless-cli/build.gradle
Normal file
66
pgpainless-cli/build.gradle
Normal file
|
@ -0,0 +1,66 @@
|
|||
plugins {
|
||||
id 'application'
|
||||
}
|
||||
def generatedVersionDir = "${buildDir}/generated-version"
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
output.dir(generatedVersionDir, builtBy: 'generateVersionProperties')
|
||||
}
|
||||
}
|
||||
|
||||
task generateVersionProperties {
|
||||
doLast {
|
||||
def propertiesFile = file "$generatedVersionDir/version.properties"
|
||||
propertiesFile.parentFile.mkdirs()
|
||||
propertiesFile.createNewFile()
|
||||
// Instead of using a Properties object here, we directly write to the file
|
||||
// since Properties adds a timestamp, ruining reproducibility
|
||||
propertiesFile.write("version="+rootProject.version.toString())
|
||||
}
|
||||
}
|
||||
processResources.dependsOn generateVersionProperties
|
||||
|
||||
dependencies {
|
||||
implementation(project(":pgpainless-sop"))
|
||||
implementation(project(":sop-java"))
|
||||
|
||||
implementation(project(":sop-java-picocli"))
|
||||
implementation 'info.picocli:picocli:4.5.2'
|
||||
|
||||
testImplementation(project(":pgpainless-core"))
|
||||
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
||||
|
||||
// https://todd.ginsberg.com/post/testing-system-exit/
|
||||
testImplementation 'com.ginsberg:junit5-system-exit:1.1.1'
|
||||
|
||||
/*
|
||||
implementation "org.bouncycastle:bcprov-debug-jdk15on:$bouncyCastleVersion"
|
||||
/*/
|
||||
implementation "org.bouncycastle:bcprov-jdk15on:$bouncyCastleVersion"
|
||||
//*/
|
||||
implementation "org.bouncycastle:bcpg-jdk15on:$bouncyCastleVersion"
|
||||
|
||||
// https://mvnrepository.com/artifact/com.google.code.findbugs/jsr305
|
||||
implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
|
||||
}
|
||||
|
||||
mainClassName = 'org.pgpainless.cli.PGPainlessCLI'
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes 'Main-Class': "$mainClassName"
|
||||
}
|
||||
|
||||
from {
|
||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
} {
|
||||
exclude "META-INF/*.SF"
|
||||
exclude "META-INF/*.DSA"
|
||||
exclude "META-INF/*.RSA"
|
||||
}
|
||||
}
|
||||
|
||||
tasks."jar".dependsOn(":pgpainless-core:assemble")
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.cli;
|
||||
|
||||
import org.pgpainless.sop.SOPImpl;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
|
||||
public class PGPainlessCLI {
|
||||
|
||||
static {
|
||||
SopCLI.setSopInstance(new SOPImpl());
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
int result = execute(args);
|
||||
if (result != 0) {
|
||||
System.exit(result);
|
||||
}
|
||||
}
|
||||
|
||||
public static int execute(String... args) {
|
||||
return SopCLI.execute(args);
|
||||
}
|
||||
}
|
|
@ -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.cli;
|
|
@ -13,27 +13,29 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
package org.pgpainless.cli;
|
||||
|
||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
||||
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import picocli.CommandLine;
|
||||
|
||||
public class ExitCodeTest {
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(69)
|
||||
public void testUnknownCommand_69() {
|
||||
assertEquals(69, new CommandLine(new PGPainlessCLI()).execute("generate-kex"));
|
||||
PGPainlessCLI.main(new String[] {"generate-kex"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(37)
|
||||
public void testCommandWithUnknownOption_37() {
|
||||
assertEquals(37, new CommandLine(new PGPainlessCLI()).execute("generate-key", "-k", "\"k is unknown\""));
|
||||
PGPainlessCLI.main(new String[] {"generate-key", "-k", "\"k is unknown\""});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void successfulVersion_0 () {
|
||||
assertEquals(0, new CommandLine(new PGPainlessCLI()).execute("version"));
|
||||
@FailOnSystemExit
|
||||
public void successfulExecutionDoesNotTerminateJVM() {
|
||||
PGPainlessCLI.main(new String[] {"version"});
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop;
|
||||
package org.pgpainless.cli;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
package org.pgpainless.cli.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
@ -28,6 +28,7 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
|
@ -35,8 +36,7 @@ import org.junit.jupiter.api.AfterEach;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.sop.PGPainlessCLI;
|
||||
import picocli.CommandLine;
|
||||
import org.pgpainless.cli.PGPainlessCLI;
|
||||
|
||||
public class ArmorTest {
|
||||
|
||||
|
@ -53,6 +53,7 @@ public class ArmorTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@FailOnSystemExit
|
||||
public void armorSecretKey() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
||||
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
||||
.modernKeyRing("alice@pgpainless.org", null);
|
||||
|
@ -61,13 +62,14 @@ public class ArmorTest {
|
|||
System.setIn(new ByteArrayInputStream(bytes));
|
||||
ByteArrayOutputStream armorOut = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(armorOut));
|
||||
new CommandLine(new PGPainlessCLI()).execute("armor");
|
||||
PGPainlessCLI.execute("armor");
|
||||
|
||||
PGPSecretKeyRing armored = PGPainless.readKeyRing().secretKeyRing(armorOut.toString());
|
||||
assertArrayEquals(secretKey.getEncoded(), armored.getEncoded());
|
||||
}
|
||||
|
||||
@Test
|
||||
@FailOnSystemExit
|
||||
public void armorPublicKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
||||
.modernKeyRing("alice@pgpainless.org", null);
|
||||
|
@ -77,20 +79,21 @@ public class ArmorTest {
|
|||
System.setIn(new ByteArrayInputStream(bytes));
|
||||
ByteArrayOutputStream armorOut = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(armorOut));
|
||||
new CommandLine(new PGPainlessCLI()).execute("armor");
|
||||
PGPainlessCLI.execute("armor");
|
||||
|
||||
PGPPublicKeyRing armored = PGPainless.readKeyRing().publicKeyRing(armorOut.toString());
|
||||
assertArrayEquals(publicKey.getEncoded(), armored.getEncoded());
|
||||
}
|
||||
|
||||
@Test
|
||||
@FailOnSystemExit
|
||||
public void armorMessage() {
|
||||
String message = "Hello, World!\n";
|
||||
|
||||
System.setIn(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream armorOut = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(armorOut));
|
||||
new CommandLine(new PGPainlessCLI()).execute("armor");
|
||||
PGPainlessCLI.execute("armor");
|
||||
|
||||
String armored = armorOut.toString();
|
||||
|
||||
|
@ -99,6 +102,7 @@ public class ArmorTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@FailOnSystemExit
|
||||
public void doesNotNestArmorByDefault() {
|
||||
String armored = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: BCPG v1.69\n" +
|
||||
|
@ -110,12 +114,13 @@ public class ArmorTest {
|
|||
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
new CommandLine(new PGPainlessCLI()).execute("armor");
|
||||
PGPainlessCLI.execute("armor");
|
||||
|
||||
assertEquals(armored, out.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@FailOnSystemExit
|
||||
public void testAllowNested() {
|
||||
String armored = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: BCPG v1.69\n" +
|
||||
|
@ -127,7 +132,7 @@ public class ArmorTest {
|
|||
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
new CommandLine(new PGPainlessCLI()).execute("armor", "--allow-nested");
|
||||
PGPainlessCLI.execute("armor", "--allow-nested");
|
||||
|
||||
assertNotEquals(armored, out.toString());
|
||||
assertTrue(out.toString().contains(
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
package org.pgpainless.cli.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
@ -26,6 +26,7 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
|
@ -33,8 +34,7 @@ import org.junit.jupiter.api.AfterEach;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.sop.PGPainlessCLI;
|
||||
import picocli.CommandLine;
|
||||
import org.pgpainless.cli.PGPainlessCLI;
|
||||
|
||||
public class DearmorTest {
|
||||
|
||||
|
@ -51,6 +51,7 @@ public class DearmorTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@FailOnSystemExit
|
||||
public void dearmorSecretKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
||||
.modernKeyRing("alice@pgpainless.org", null);
|
||||
|
@ -59,13 +60,14 @@ public class DearmorTest {
|
|||
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
new CommandLine(new PGPainlessCLI()).execute("dearmor");
|
||||
PGPainlessCLI.execute("dearmor");
|
||||
|
||||
assertArrayEquals(secretKey.getEncoded(), out.toByteArray());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@FailOnSystemExit
|
||||
public void dearmorCertificate() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
||||
.modernKeyRing("alice@pgpainless.org", null);
|
||||
|
@ -75,12 +77,13 @@ public class DearmorTest {
|
|||
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
new CommandLine(new PGPainlessCLI()).execute("dearmor");
|
||||
PGPainlessCLI.execute("dearmor");
|
||||
|
||||
assertArrayEquals(certificate.getEncoded(), out.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
@FailOnSystemExit
|
||||
public void dearmorMessage() {
|
||||
String armored = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: BCPG v1.69\n" +
|
||||
|
@ -92,7 +95,7 @@ public class DearmorTest {
|
|||
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
new CommandLine(new PGPainlessCLI()).execute("dearmor");
|
||||
PGPainlessCLI.execute("dearmor");
|
||||
|
||||
assertEquals("Hello, World\n", out.toString());
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
package org.pgpainless.cli.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -28,12 +28,12 @@ import java.io.OutputStream;
|
|||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.sop.PGPainlessCLI;
|
||||
import org.pgpainless.sop.TestUtils;
|
||||
import picocli.CommandLine;
|
||||
import org.pgpainless.cli.PGPainlessCLI;
|
||||
import org.pgpainless.cli.TestUtils;
|
||||
|
||||
public class EncryptDecryptTest {
|
||||
|
||||
|
@ -46,7 +46,8 @@ public class EncryptDecryptTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test() throws IOException {
|
||||
@FailOnSystemExit
|
||||
public void encryptAndDecryptAMessage() throws IOException {
|
||||
originalSout = System.out;
|
||||
File julietKeyFile = new File(tempDir, "juliet.key");
|
||||
assertTrue(julietKeyFile.createNewFile());
|
||||
|
@ -65,27 +66,27 @@ public class EncryptDecryptTest {
|
|||
|
||||
OutputStream julietKeyOut = new FileOutputStream(julietKeyFile);
|
||||
System.setOut(new PrintStream(julietKeyOut));
|
||||
new CommandLine(new PGPainlessCLI()).execute("generate-key", "Juliet Capulet <juliet@capulet.lit>");
|
||||
PGPainlessCLI.execute("generate-key", "Juliet Capulet <juliet@capulet.lit>");
|
||||
julietKeyOut.close();
|
||||
|
||||
FileInputStream julietKeyIn = new FileInputStream(julietKeyFile);
|
||||
System.setIn(julietKeyIn);
|
||||
OutputStream julietCertOut = new FileOutputStream(julietCertFile);
|
||||
System.setOut(new PrintStream(julietCertOut));
|
||||
new CommandLine(new PGPainlessCLI()).execute("extract-cert");
|
||||
PGPainlessCLI.execute("extract-cert");
|
||||
julietKeyIn.close();
|
||||
julietCertOut.close();
|
||||
|
||||
OutputStream romeoKeyOut = new FileOutputStream(romeoKeyFile);
|
||||
System.setOut(new PrintStream(romeoKeyOut));
|
||||
new CommandLine(new PGPainlessCLI()).execute("generate-key", "Romeo Montague <romeo@montague.lit>");
|
||||
PGPainlessCLI.execute("generate-key", "Romeo Montague <romeo@montague.lit>");
|
||||
romeoKeyOut.close();
|
||||
|
||||
FileInputStream romeoKeyIn = new FileInputStream(romeoKeyFile);
|
||||
System.setIn(romeoKeyIn);
|
||||
OutputStream romeoCertOut = new FileOutputStream(romeoCertFile);
|
||||
System.setOut(new PrintStream(romeoCertOut));
|
||||
new CommandLine(new PGPainlessCLI()).execute("extract-cert");
|
||||
PGPainlessCLI.execute("extract-cert");
|
||||
romeoKeyIn.close();
|
||||
romeoCertOut.close();
|
||||
|
||||
|
@ -94,7 +95,7 @@ public class EncryptDecryptTest {
|
|||
System.setIn(msgIn);
|
||||
OutputStream msgAscOut = new FileOutputStream(msgAscFile);
|
||||
System.setOut(new PrintStream(msgAscOut));
|
||||
new CommandLine(new PGPainlessCLI()).execute("encrypt",
|
||||
PGPainlessCLI.execute("encrypt",
|
||||
"--sign-with", romeoKeyFile.getAbsolutePath(),
|
||||
julietCertFile.getAbsolutePath());
|
||||
msgAscOut.close();
|
||||
|
@ -107,7 +108,7 @@ public class EncryptDecryptTest {
|
|||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
PrintStream pOut = new PrintStream(out);
|
||||
System.setOut(pOut);
|
||||
new CommandLine(new PGPainlessCLI()).execute("decrypt",
|
||||
PGPainlessCLI.execute("decrypt",
|
||||
"--verify-out", verifyFile.getAbsolutePath(),
|
||||
"--verify-with", romeoCertFile.getAbsolutePath(),
|
||||
julietKeyFile.getAbsolutePath());
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
package org.pgpainless.cli.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -25,18 +25,19 @@ import java.io.PrintStream;
|
|||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.cli.PGPainlessCLI;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.sop.PGPainlessCLI;
|
||||
import picocli.CommandLine;
|
||||
|
||||
public class ExtractCertTest {
|
||||
|
||||
@Test
|
||||
@FailOnSystemExit
|
||||
public void testExtractCert() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
|
||||
.simpleEcKeyRing("Juliet Capulet <juliet@capulet.lit>");
|
||||
|
@ -46,7 +47,7 @@ public class ExtractCertTest {
|
|||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
|
||||
new CommandLine(new PGPainlessCLI()).execute("extract-cert");
|
||||
PGPainlessCLI.execute("extract-cert");
|
||||
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(out.toByteArray());
|
||||
KeyRingInfo info = PGPainless.inspectKeyRing(publicKeys);
|
||||
assertFalse(info.isSecretKey());
|
|
@ -13,12 +13,12 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
package org.pgpainless.cli.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.pgpainless.sop.TestUtils.ARMOR_PRIVATE_KEY_HEADER_BYTES;
|
||||
import static org.pgpainless.cli.TestUtils.ARMOR_PRIVATE_KEY_HEADER_BYTES;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
|
@ -26,15 +26,15 @@ import java.io.IOException;
|
|||
import java.io.PrintStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.cli.PGPainlessCLI;
|
||||
import org.pgpainless.cli.TestUtils;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.sop.PGPainlessCLI;
|
||||
import org.pgpainless.sop.TestUtils;
|
||||
import picocli.CommandLine;
|
||||
|
||||
public class GenerateCertTest {
|
||||
|
||||
|
@ -47,10 +47,11 @@ public class GenerateCertTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@FailOnSystemExit
|
||||
public void testKeyGeneration() throws IOException, PGPException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
new CommandLine(new PGPainlessCLI()).execute("generate-key", "--armor", "Juliet Capulet <juliet@capulet.lit>");
|
||||
PGPainlessCLI.execute("generate-key", "--armor", "Juliet Capulet <juliet@capulet.lit>");
|
||||
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(out.toByteArray());
|
||||
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
|
||||
|
@ -62,10 +63,11 @@ public class GenerateCertTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@FailOnSystemExit
|
||||
public void testNoArmor() {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
new CommandLine(new PGPainlessCLI()).execute("generate-key", "--no-armor", "Test <test@test.test>");
|
||||
PGPainlessCLI.execute("generate-key", "--no-armor", "Test <test@test.test>");
|
||||
|
||||
byte[] outBegin = new byte[37];
|
||||
System.arraycopy(out.toByteArray(), 0, outBegin, 0, 37);
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
package org.pgpainless.cli.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -32,6 +32,7 @@ import java.security.InvalidAlgorithmParameterException;
|
|||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Date;
|
||||
|
||||
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
|
@ -40,12 +41,11 @@ import org.junit.jupiter.api.AfterAll;
|
|||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.cli.PGPainlessCLI;
|
||||
import org.pgpainless.cli.TestUtils;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.key.util.KeyRingUtils;
|
||||
import org.pgpainless.sop.PGPainlessCLI;
|
||||
import org.pgpainless.sop.TestUtils;
|
||||
import picocli.CommandLine;
|
||||
|
||||
public class SignVerifyTest {
|
||||
|
||||
|
@ -60,6 +60,7 @@ public class SignVerifyTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@FailOnSystemExit
|
||||
public void testSignatureCreationAndVerification() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
originalSout = System.out;
|
||||
InputStream originalIn = System.in;
|
||||
|
@ -95,7 +96,7 @@ public class SignVerifyTest {
|
|||
assertTrue(sigFile.createNewFile());
|
||||
FileOutputStream sigOut = new FileOutputStream(sigFile);
|
||||
System.setOut(new PrintStream(sigOut));
|
||||
new CommandLine(new PGPainlessCLI()).execute("sign", "--armor", aliceKeyFile.getAbsolutePath());
|
||||
PGPainlessCLI.execute("sign", "--armor", aliceKeyFile.getAbsolutePath());
|
||||
sigOut.close();
|
||||
|
||||
// verify test data signature
|
||||
|
@ -103,7 +104,7 @@ public class SignVerifyTest {
|
|||
System.setOut(new PrintStream(verifyOut));
|
||||
dataIn = new FileInputStream(dataFile);
|
||||
System.setIn(dataIn);
|
||||
new CommandLine(new PGPainlessCLI()).execute("verify", sigFile.getAbsolutePath(), aliceCertFile.getAbsolutePath());
|
||||
PGPainlessCLI.execute("verify", sigFile.getAbsolutePath(), aliceCertFile.getAbsolutePath());
|
||||
dataIn.close();
|
||||
|
||||
// Test verification output
|
||||
|
@ -113,8 +114,8 @@ public class SignVerifyTest {
|
|||
String[] split = verification.split(" ");
|
||||
OpenPgpV4Fingerprint primaryKeyFingerprint = new OpenPgpV4Fingerprint(aliceKeys);
|
||||
OpenPgpV4Fingerprint signingKeyFingerprint = new OpenPgpV4Fingerprint(new KeyRingInfo(alicePub, new Date()).getSigningSubkeys().get(0));
|
||||
assertEquals(signingKeyFingerprint.toString(), split[1]);
|
||||
assertEquals(primaryKeyFingerprint.toString(), split[2]);
|
||||
assertEquals(signingKeyFingerprint.toString(), split[1].trim());
|
||||
assertEquals(primaryKeyFingerprint.toString(), split[2].trim());
|
||||
|
||||
System.setIn(originalIn);
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
package org.pgpainless.decryption_verification;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
|
@ -76,8 +77,10 @@ public final class DecryptionStreamFactory {
|
|||
private final ConsumerOptions options;
|
||||
|
||||
private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
|
||||
private static final PGPContentVerifierBuilderProvider verifierBuilderProvider = ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider();
|
||||
private static final KeyFingerPrintCalculator keyFingerprintCalculator = ImplementationFactory.getInstance().getKeyFingerprintCalculator();
|
||||
private static final PGPContentVerifierBuilderProvider verifierBuilderProvider =
|
||||
ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider();
|
||||
private static final KeyFingerPrintCalculator keyFingerprintCalculator =
|
||||
ImplementationFactory.getInstance().getKeyFingerprintCalculator();
|
||||
private final Map<OpenPgpV4Fingerprint, OnePassSignature> verifiableOnePassSignatures = new HashMap<>();
|
||||
private final List<IntegrityProtectedInputStream> integrityProtectedStreams = new ArrayList<>();
|
||||
|
||||
|
@ -109,11 +112,23 @@ public final class DecryptionStreamFactory {
|
|||
try {
|
||||
// Parse OpenPGP message
|
||||
inputStream = factory.processPGPPackets(objectFactory, 1);
|
||||
} catch (MissingLiteralDataException e) {
|
||||
// Not an OpenPGP message. Reset the buffered stream to parse the message as arbitrary binary data
|
||||
} catch (EOFException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (MissingLiteralDataException e) {
|
||||
// Not an OpenPGP message.
|
||||
// Reset the buffered stream to parse the message as arbitrary binary data
|
||||
// to allow for detached signature verification.
|
||||
bufferedIn.reset();
|
||||
inputStream = bufferedIn;
|
||||
} catch (IOException e) {
|
||||
if (e.getMessage().contains("invalid armor")) {
|
||||
// We falsely assumed the data to be armored.
|
||||
bufferedIn.reset();
|
||||
inputStream = bufferedIn;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return new DecryptionStream(inputStream, factory.resultBuilder, factory.integrityProtectedStreams);
|
||||
|
@ -146,7 +161,9 @@ public final class DecryptionStreamFactory {
|
|||
throws PGPException, IOException {
|
||||
LOGGER.log(LEVEL, "Encountered PGPEncryptedDataList");
|
||||
InputStream decryptedDataStream = decrypt(pgpEncryptedDataList);
|
||||
return processPGPPackets(new PGPObjectFactory(PGPUtil.getDecoderStream(decryptedDataStream), keyFingerprintCalculator), ++depth);
|
||||
InputStream decodedDataStream = PGPUtil.getDecoderStream(decryptedDataStream);
|
||||
PGPObjectFactory factory = new PGPObjectFactory(decodedDataStream, keyFingerprintCalculator);
|
||||
return processPGPPackets(factory, ++depth);
|
||||
}
|
||||
|
||||
private InputStream processPGPCompressedData(PGPCompressedData pgpCompressedData, int depth)
|
||||
|
@ -155,8 +172,9 @@ public final class DecryptionStreamFactory {
|
|||
LOGGER.log(LEVEL, "Encountered PGPCompressedData: " + compressionAlgorithm);
|
||||
resultBuilder.setCompressionAlgorithm(compressionAlgorithm);
|
||||
|
||||
InputStream dataStream = pgpCompressedData.getDataStream();
|
||||
PGPObjectFactory objectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(dataStream), keyFingerprintCalculator);
|
||||
InputStream inflatedDataStream = pgpCompressedData.getDataStream();
|
||||
InputStream decodedDataStream = PGPUtil.getDecoderStream(inflatedDataStream);
|
||||
PGPObjectFactory objectFactory = new PGPObjectFactory(decodedDataStream, keyFingerprintCalculator);
|
||||
|
||||
return processPGPPackets(objectFactory, ++depth);
|
||||
}
|
||||
|
@ -171,11 +189,10 @@ public final class DecryptionStreamFactory {
|
|||
private InputStream processPGPLiteralData(@Nonnull PGPObjectFactory objectFactory, PGPLiteralData pgpLiteralData) {
|
||||
LOGGER.log(LEVEL, "Found PGPLiteralData");
|
||||
InputStream literalDataInputStream = pgpLiteralData.getInputStream();
|
||||
OpenPgpMetadata.FileInfo fileInfo = new OpenPgpMetadata.FileInfo(
|
||||
pgpLiteralData.getFileName(),
|
||||
pgpLiteralData.getModificationTime(),
|
||||
StreamEncoding.fromCode(pgpLiteralData.getFormat()));
|
||||
resultBuilder.setFileInfo(fileInfo);
|
||||
|
||||
resultBuilder.setFileName(pgpLiteralData.getFileName())
|
||||
.setModificationDate(pgpLiteralData.getModificationTime())
|
||||
.setFileEncoding(StreamEncoding.fromCode(pgpLiteralData.getFormat()));
|
||||
|
||||
if (verifiableOnePassSignatures.isEmpty()) {
|
||||
LOGGER.log(LEVEL, "No OnePassSignatures found -> We are done");
|
||||
|
@ -226,9 +243,6 @@ public final class DecryptionStreamFactory {
|
|||
|
||||
// data is public key encrypted
|
||||
else if (encryptedData instanceof PGPPublicKeyEncryptedData) {
|
||||
if (options.getDecryptionKeys().isEmpty()) {
|
||||
|
||||
}
|
||||
PGPPublicKeyEncryptedData publicKeyEncryptedData = (PGPPublicKeyEncryptedData) encryptedData;
|
||||
long keyId = publicKeyEncryptedData.getKeyID();
|
||||
if (!options.getDecryptionKeys().isEmpty()) {
|
||||
|
|
|
@ -24,6 +24,8 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
|
@ -44,7 +46,9 @@ public class OpenPgpMetadata {
|
|||
private final List<DetachedSignature> detachedSignatures;
|
||||
private final SymmetricKeyAlgorithm symmetricKeyAlgorithm;
|
||||
private final CompressionAlgorithm compressionAlgorithm;
|
||||
private final FileInfo fileInfo;
|
||||
private final String fileName;
|
||||
private final Date modificationDate;
|
||||
private final StreamEncoding fileEncoding;
|
||||
|
||||
public OpenPgpMetadata(Set<Long> recipientKeyIds,
|
||||
SubkeyIdentifier decryptionKey,
|
||||
|
@ -52,7 +56,9 @@ public class OpenPgpMetadata {
|
|||
CompressionAlgorithm algorithm,
|
||||
List<OnePassSignature> onePassSignatures,
|
||||
List<DetachedSignature> detachedSignatures,
|
||||
FileInfo fileInfo) {
|
||||
String fileName,
|
||||
Date modificationDate,
|
||||
StreamEncoding fileEncoding) {
|
||||
|
||||
this.recipientKeyIds = Collections.unmodifiableSet(recipientKeyIds);
|
||||
this.decryptionKey = decryptionKey;
|
||||
|
@ -60,7 +66,9 @@ public class OpenPgpMetadata {
|
|||
this.compressionAlgorithm = algorithm;
|
||||
this.detachedSignatures = Collections.unmodifiableList(detachedSignatures);
|
||||
this.onePassSignatures = Collections.unmodifiableList(onePassSignatures);
|
||||
this.fileInfo = fileInfo;
|
||||
this.fileName = fileName;
|
||||
this.modificationDate = modificationDate;
|
||||
this.fileEncoding = fileEncoding;
|
||||
}
|
||||
|
||||
public Set<Long> getRecipientKeyIds() {
|
||||
|
@ -148,12 +156,59 @@ public class OpenPgpMetadata {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return information about the encrypted/signed file.
|
||||
*
|
||||
* @deprecated use {@link #getFileName()}, {@link #getModificationDate()} and {@link #getFileEncoding()} instead.
|
||||
* @return file info
|
||||
*/
|
||||
@Deprecated
|
||||
public FileInfo getFileInfo() {
|
||||
return fileInfo;
|
||||
return new FileInfo(
|
||||
getFileName(),
|
||||
getModificationDate(),
|
||||
getFileEncoding()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the encrypted / signed file.
|
||||
*
|
||||
* @return file name
|
||||
*/
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true, if the encrypted data is intended for your eyes only.
|
||||
*
|
||||
* @return true if for-your-eyes-only
|
||||
*/
|
||||
public boolean isForYourEyesOnly() {
|
||||
return PGPLiteralData.CONSOLE.equals(getFileName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the modification date of the encrypted / signed file.
|
||||
*
|
||||
* @return modification date
|
||||
*/
|
||||
public Date getModificationDate() {
|
||||
return modificationDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the encoding format of the encrypted / signed file.
|
||||
*
|
||||
* @return encoding
|
||||
*/
|
||||
public StreamEncoding getFileEncoding() {
|
||||
return fileEncoding;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static class FileInfo {
|
||||
public static final String FOR_YOUR_EYES_ONLY = PGPLiteralData.CONSOLE;
|
||||
|
||||
protected final String fileName;
|
||||
protected final Date modificationDate;
|
||||
|
@ -165,22 +220,10 @@ public class OpenPgpMetadata {
|
|||
this.streamEncoding = streamEncoding;
|
||||
}
|
||||
|
||||
public static FileInfo binaryStream() {
|
||||
return new FileInfo("", null, StreamEncoding.BINARY);
|
||||
}
|
||||
|
||||
public static FileInfo forYourEyesOnly() {
|
||||
return new FileInfo(FOR_YOUR_EYES_ONLY, null, StreamEncoding.BINARY);
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public boolean isForYourEyesOnly() {
|
||||
return FOR_YOUR_EYES_ONLY.equals(fileName);
|
||||
}
|
||||
|
||||
public Date getModificationDate() {
|
||||
return modificationDate;
|
||||
}
|
||||
|
@ -243,7 +286,9 @@ public class OpenPgpMetadata {
|
|||
private final List<OnePassSignature> onePassSignatures = new ArrayList<>();
|
||||
private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.NULL;
|
||||
private CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED;
|
||||
private FileInfo fileInfo;
|
||||
private String fileName;
|
||||
private StreamEncoding fileEncoding;
|
||||
private Date modificationDate;
|
||||
|
||||
public Builder addRecipientKeyId(Long keyId) {
|
||||
this.recipientFingerprints.add(keyId);
|
||||
|
@ -269,6 +314,21 @@ public class OpenPgpMetadata {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder setFileName(@Nonnull String fileName) {
|
||||
this.fileName = fileName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setModificationDate(Date modificationDate) {
|
||||
this.modificationDate = modificationDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFileEncoding(StreamEncoding encoding) {
|
||||
this.fileEncoding = encoding;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void addDetachedSignature(DetachedSignature signature) {
|
||||
this.detachedSignatures.add(signature);
|
||||
}
|
||||
|
@ -277,15 +337,10 @@ public class OpenPgpMetadata {
|
|||
this.onePassSignatures.add(onePassSignature);
|
||||
}
|
||||
|
||||
public Builder setFileInfo(FileInfo fileInfo) {
|
||||
this.fileInfo = fileInfo;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OpenPgpMetadata build() {
|
||||
return new OpenPgpMetadata(recipientFingerprints, decryptionKey,
|
||||
symmetricKeyAlgorithm, compressionAlgorithm,
|
||||
onePassSignatures, detachedSignatures, fileInfo);
|
||||
onePassSignatures, detachedSignatures, fileName, modificationDate, fileEncoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,16 @@
|
|||
package org.pgpainless.encryption_signing;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.StreamEncoding;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
|
@ -33,18 +38,24 @@ public final class EncryptionResult {
|
|||
|
||||
private final MultiMap<SubkeyIdentifier, PGPSignature> detachedSignatures;
|
||||
private final Set<SubkeyIdentifier> recipients;
|
||||
private final OpenPgpMetadata.FileInfo fileInfo;
|
||||
private final String fileName;
|
||||
private final Date modificationDate;
|
||||
private final StreamEncoding fileEncoding;
|
||||
|
||||
private EncryptionResult(SymmetricKeyAlgorithm encryptionAlgorithm,
|
||||
CompressionAlgorithm compressionAlgorithm,
|
||||
MultiMap<SubkeyIdentifier, PGPSignature> detachedSignatures,
|
||||
Set<SubkeyIdentifier> recipients,
|
||||
OpenPgpMetadata.FileInfo fileInfo) {
|
||||
String fileName,
|
||||
Date modificationDate,
|
||||
StreamEncoding encoding) {
|
||||
this.encryptionAlgorithm = encryptionAlgorithm;
|
||||
this.compressionAlgorithm = compressionAlgorithm;
|
||||
this.detachedSignatures = detachedSignatures;
|
||||
this.recipients = Collections.unmodifiableSet(recipients);
|
||||
this.fileInfo = fileInfo;
|
||||
this.fileName = fileName;
|
||||
this.modificationDate = modificationDate;
|
||||
this.fileEncoding = encoding;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
|
@ -68,14 +79,37 @@ public final class EncryptionResult {
|
|||
return recipients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return information about the encrypted / signed data.
|
||||
*
|
||||
* @deprecated use {@link #getFileName()}, {@link #getModificationDate()} and {@link #getFileEncoding()} instead.
|
||||
* @return info
|
||||
*/
|
||||
@Deprecated
|
||||
public OpenPgpMetadata.FileInfo getFileInfo() {
|
||||
return fileInfo;
|
||||
return new OpenPgpMetadata.FileInfo(getFileName(), getModificationDate(), getFileEncoding());
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public Date getModificationDate() {
|
||||
return modificationDate;
|
||||
}
|
||||
|
||||
public StreamEncoding getFileEncoding() {
|
||||
return fileEncoding;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public boolean isForYourEyesOnly() {
|
||||
return PGPLiteralData.CONSOLE.equals(getFileName());
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private SymmetricKeyAlgorithm encryptionAlgorithm;
|
||||
|
@ -83,7 +117,9 @@ public final class EncryptionResult {
|
|||
|
||||
private final MultiMap<SubkeyIdentifier, PGPSignature> detachedSignatures = new MultiMap<>();
|
||||
private Set<SubkeyIdentifier> recipients = new HashSet<>();
|
||||
private OpenPgpMetadata.FileInfo fileInfo;
|
||||
private String fileName = "";
|
||||
private Date modificationDate = new Date(0L); // NOW
|
||||
private StreamEncoding encoding = StreamEncoding.BINARY;
|
||||
|
||||
public Builder setEncryptionAlgorithm(SymmetricKeyAlgorithm encryptionAlgorithm) {
|
||||
this.encryptionAlgorithm = encryptionAlgorithm;
|
||||
|
@ -105,8 +141,18 @@ public final class EncryptionResult {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder setFileInfo(OpenPgpMetadata.FileInfo fileInfo) {
|
||||
this.fileInfo = fileInfo;
|
||||
public Builder setFileName(@Nonnull String fileName) {
|
||||
this.fileName = fileName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setModificationDate(@Nonnull Date modificationDate) {
|
||||
this.modificationDate = modificationDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFileEncoding(StreamEncoding fileEncoding) {
|
||||
this.encoding = fileEncoding;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -117,11 +163,9 @@ public final class EncryptionResult {
|
|||
if (compressionAlgorithm == null) {
|
||||
throw new IllegalStateException("Compression algorithm not set.");
|
||||
}
|
||||
if (fileInfo == null) {
|
||||
throw new IllegalStateException("File info not set.");
|
||||
}
|
||||
|
||||
return new EncryptionResult(encryptionAlgorithm, compressionAlgorithm, detachedSignatures, recipients, fileInfo);
|
||||
return new EncryptionResult(encryptionAlgorithm, compressionAlgorithm, detachedSignatures, recipients,
|
||||
fileName, modificationDate, encoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder;
|
|||
import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.implementation.ImplementationFactory;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.util.ArmoredOutputStreamFactory;
|
||||
|
@ -154,8 +153,9 @@ public final class EncryptionStream extends OutputStream {
|
|||
new byte[BUFFER_SIZE]);
|
||||
outermostStream = literalDataStream;
|
||||
|
||||
resultBuilder.setFileInfo(new OpenPgpMetadata.FileInfo(
|
||||
options.getFileName(), options.getModificationDate(), options.getEncoding()));
|
||||
resultBuilder.setFileName(options.getFileName())
|
||||
.setModificationDate(options.getModificationDate())
|
||||
.setFileEncoding(options.getEncoding());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -256,4 +256,8 @@ public final class EncryptionStream extends OutputStream {
|
|||
}
|
||||
return resultBuilder.build();
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,11 +33,14 @@ import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
|
|||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.exception.KeyCannotSignException;
|
||||
import org.pgpainless.exception.KeyValidationException;
|
||||
import org.pgpainless.implementation.ImplementationFactory;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.key.protection.UnlockSecretKey;
|
||||
import org.pgpainless.policy.Policy;
|
||||
|
||||
public final class SigningOptions {
|
||||
|
@ -161,12 +164,12 @@ public final class SigningOptions {
|
|||
|
||||
List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
|
||||
if (signingPubKeys.isEmpty()) {
|
||||
throw new AssertionError("Key has no valid signing key.");
|
||||
throw new KeyCannotSignException("Key " + new OpenPgpV4Fingerprint(secretKey) + " has no valid signing key.");
|
||||
}
|
||||
|
||||
for (PGPPublicKey signingPubKey : signingPubKeys) {
|
||||
PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
|
||||
PGPPrivateKey signingSubkey = signingSecKey.extractPrivateKey(secretKeyDecryptor.getDecryptor(signingPubKey.getKeyID()));
|
||||
PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor);
|
||||
Set<HashAlgorithm> hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(userId, signingPubKey.getKeyID());
|
||||
HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
|
||||
addSigningMethod(secretKey, signingSubkey, hashAlgorithm, signatureType, false);
|
||||
|
@ -242,7 +245,7 @@ public final class SigningOptions {
|
|||
|
||||
List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
|
||||
if (signingPubKeys.isEmpty()) {
|
||||
throw new AssertionError("Key has no valid signing key.");
|
||||
throw new KeyCannotSignException("Key has no valid signing key.");
|
||||
}
|
||||
|
||||
for (PGPPublicKey signingPubKey : signingPubKeys) {
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2021 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.exception;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
|
||||
public class KeyCannotSignException extends PGPException {
|
||||
public KeyCannotSignException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ import java.io.ByteArrayInputStream;
|
|||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
@ -68,6 +69,23 @@ public class ArmorUtils {
|
|||
return sb.toString();
|
||||
}
|
||||
|
||||
public static ArmoredOutputStream toAsciiArmoredStream(PGPKeyRing keyRing, OutputStream outputStream) {
|
||||
MultiMap<String, String> header = keyToHeader(keyRing);
|
||||
return toAsciiArmoredStream(outputStream, header);
|
||||
}
|
||||
|
||||
public static ArmoredOutputStream toAsciiArmoredStream(OutputStream outputStream, MultiMap<String, String> header) {
|
||||
ArmoredOutputStream armoredOutputStream = ArmoredOutputStreamFactory.get(outputStream);
|
||||
if (header != null) {
|
||||
for (String headerKey : header.keySet()) {
|
||||
for (String headerValue : header.get(headerKey)) {
|
||||
armoredOutputStream.addHeader(headerKey, headerValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
return armoredOutputStream;
|
||||
}
|
||||
|
||||
public static String toAsciiArmoredString(PGPPublicKeyRingCollection publicKeyRings) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Iterator<PGPPublicKeyRing> iterator = publicKeyRings.iterator(); iterator.hasNext(); ) {
|
||||
|
@ -124,20 +142,25 @@ public class ArmorUtils {
|
|||
|
||||
public static String toAsciiArmoredString(InputStream inputStream, MultiMap<String, String> additionalHeaderValues) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ArmoredOutputStream armor = ArmoredOutputStreamFactory.get(out);
|
||||
if (additionalHeaderValues != null) {
|
||||
for (String header : additionalHeaderValues.keySet()) {
|
||||
for (String value : additionalHeaderValues.get(header)) {
|
||||
armor.addHeader(header, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
ArmoredOutputStream armor = toAsciiArmoredStream(out, additionalHeaderValues);
|
||||
Streams.pipeAll(inputStream, armor);
|
||||
armor.close();
|
||||
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
public static ArmoredOutputStream createArmoredOutputStreamFor(PGPKeyRing keyRing, OutputStream outputStream) {
|
||||
ArmoredOutputStream armor = ArmoredOutputStreamFactory.get(outputStream);
|
||||
MultiMap<String, String> headerMap = keyToHeader(keyRing);
|
||||
for (String header : headerMap.keySet()) {
|
||||
for (String value : headerMap.get(header)) {
|
||||
armor.addHeader(header, value);
|
||||
}
|
||||
}
|
||||
|
||||
return armor;
|
||||
}
|
||||
|
||||
public static List<String> getCommendHeaderValues(ArmoredInputStream armor) {
|
||||
return getArmorHeaderValues(armor, HEADER_COMMENT);
|
||||
}
|
||||
|
|
|
@ -17,9 +17,17 @@ package org.junit;
|
|||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.pgpainless.util.DateUtil;
|
||||
|
||||
public class JUtils {
|
||||
|
||||
public static void assertEquals(long a, long b, long delta) {
|
||||
assertTrue(a - delta <= b && a + delta >= b);
|
||||
}
|
||||
|
||||
public static void assertDateEquals(Date a, Date b) {
|
||||
org.junit.jupiter.api.Assertions.assertEquals(DateUtil.formatUTCDate(a), DateUtil.formatUTCDate(b));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 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.encryption_signing;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Date;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.StreamEncoding;
|
||||
import org.pgpainless.decryption_verification.ConsumerOptions;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.key.util.KeyRingUtils;
|
||||
|
||||
public class FileInfoTest {
|
||||
|
||||
@Test
|
||||
public void textFile() throws NoSuchAlgorithmException, PGPException, InvalidAlgorithmParameterException, IOException {
|
||||
OpenPgpMetadata.FileInfo fileInfo = new OpenPgpMetadata.FileInfo("message.txt", new Date(), StreamEncoding.TEXT);
|
||||
executeWith(fileInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void binaryStream() throws NoSuchAlgorithmException, PGPException, InvalidAlgorithmParameterException, IOException {
|
||||
OpenPgpMetadata.FileInfo fileInfo = OpenPgpMetadata.FileInfo.binaryStream();
|
||||
executeWith(fileInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forYourEyesOnly() throws NoSuchAlgorithmException, PGPException, InvalidAlgorithmParameterException, IOException {
|
||||
OpenPgpMetadata.FileInfo fileInfo = OpenPgpMetadata.FileInfo.forYourEyesOnly();
|
||||
executeWith(fileInfo);
|
||||
}
|
||||
|
||||
public void executeWith(OpenPgpMetadata.FileInfo fileInfo) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().simpleEcKeyRing("alice@wonderland.lit");
|
||||
PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys);
|
||||
|
||||
String data = "Hello, World!";
|
||||
|
||||
ByteArrayInputStream dataIn = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
|
||||
ByteArrayOutputStream dataOut = new ByteArrayOutputStream();
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(dataOut)
|
||||
.withOptions(ProducerOptions.encrypt(
|
||||
EncryptionOptions
|
||||
.encryptCommunications()
|
||||
.addRecipient(publicKeys))
|
||||
.setEncoding(fileInfo.getStreamFormat())
|
||||
.setFileName(fileInfo.getFileName())
|
||||
.setModificationDate(fileInfo.getModificationDate())
|
||||
);
|
||||
|
||||
Streams.pipeAll(dataIn, encryptionStream);
|
||||
encryptionStream.close();
|
||||
|
||||
OpenPgpMetadata.FileInfo cryptInfo = encryptionStream.getResult().getFileInfo();
|
||||
assertEquals(fileInfo, cryptInfo);
|
||||
|
||||
ByteArrayInputStream cryptIn = new ByteArrayInputStream(dataOut.toByteArray());
|
||||
ByteArrayOutputStream plainOut = new ByteArrayOutputStream();
|
||||
|
||||
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(cryptIn)
|
||||
.withOptions(new ConsumerOptions()
|
||||
.addDecryptionKey(secretKeys));
|
||||
Streams.pipeAll(decryptionStream, plainOut);
|
||||
|
||||
decryptionStream.close();
|
||||
|
||||
OpenPgpMetadata.FileInfo decryptInfo = decryptionStream.getResult().getFileInfo();
|
||||
assertEquals(fileInfo, decryptInfo);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Copyright 2021 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.encryption_signing;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Date;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.junit.JUtils;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.StreamEncoding;
|
||||
import org.pgpainless.decryption_verification.ConsumerOptions;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
|
||||
public class FileInformationTest {
|
||||
|
||||
private static final String data = "Hello, World!\n";
|
||||
private static PGPSecretKeyRing secretKey;
|
||||
private static PGPPublicKeyRing certificate;
|
||||
|
||||
@BeforeAll
|
||||
public static void generateKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
||||
secretKey = PGPainless.generateKeyRing().modernKeyRing("alice@wonderland.lit", null);
|
||||
certificate = PGPainless.extractCertificate(secretKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTextFile() throws PGPException, IOException {
|
||||
String fileName = "message.txt";
|
||||
Date modificationDate = new Date();
|
||||
StreamEncoding encoding = StreamEncoding.TEXT;
|
||||
|
||||
ByteArrayInputStream dataIn = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
|
||||
ByteArrayOutputStream dataOut = new ByteArrayOutputStream();
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(dataOut)
|
||||
.withOptions(ProducerOptions.encrypt(
|
||||
EncryptionOptions
|
||||
.encryptCommunications()
|
||||
.addRecipient(certificate))
|
||||
.setFileName(fileName)
|
||||
.setModificationDate(modificationDate)
|
||||
.setEncoding(encoding)
|
||||
);
|
||||
|
||||
Streams.pipeAll(dataIn, encryptionStream);
|
||||
encryptionStream.close();
|
||||
|
||||
EncryptionResult encResult = encryptionStream.getResult();
|
||||
assertEquals(fileName, encResult.getFileName());
|
||||
JUtils.assertDateEquals(modificationDate, encResult.getModificationDate());
|
||||
assertEquals(encoding, encResult.getFileEncoding());
|
||||
|
||||
ByteArrayInputStream cryptIn = new ByteArrayInputStream(dataOut.toByteArray());
|
||||
ByteArrayOutputStream plainOut = new ByteArrayOutputStream();
|
||||
|
||||
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(cryptIn)
|
||||
.withOptions(new ConsumerOptions()
|
||||
.addDecryptionKey(secretKey));
|
||||
Streams.pipeAll(decryptionStream, plainOut);
|
||||
|
||||
decryptionStream.close();
|
||||
|
||||
OpenPgpMetadata decResult = decryptionStream.getResult();
|
||||
|
||||
assertEquals(fileName, decResult.getFileName());
|
||||
JUtils.assertDateEquals(modificationDate, decResult.getModificationDate());
|
||||
assertEquals(encoding, decResult.getFileEncoding());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaults() throws PGPException, IOException {
|
||||
ByteArrayInputStream dataIn = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
|
||||
ByteArrayOutputStream dataOut = new ByteArrayOutputStream();
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(dataOut)
|
||||
.withOptions(ProducerOptions.encrypt(
|
||||
EncryptionOptions
|
||||
.encryptCommunications()
|
||||
.addRecipient(certificate))
|
||||
);
|
||||
|
||||
Streams.pipeAll(dataIn, encryptionStream);
|
||||
encryptionStream.close();
|
||||
|
||||
EncryptionResult encResult = encryptionStream.getResult();
|
||||
assertEquals("", encResult.getFileName());
|
||||
JUtils.assertDateEquals(PGPLiteralData.NOW, encResult.getModificationDate());
|
||||
assertEquals(PGPLiteralData.BINARY, encResult.getFileEncoding().getCode());
|
||||
assertFalse(encResult.isForYourEyesOnly());
|
||||
|
||||
ByteArrayInputStream cryptIn = new ByteArrayInputStream(dataOut.toByteArray());
|
||||
ByteArrayOutputStream plainOut = new ByteArrayOutputStream();
|
||||
|
||||
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(cryptIn)
|
||||
.withOptions(new ConsumerOptions()
|
||||
.addDecryptionKey(secretKey));
|
||||
Streams.pipeAll(decryptionStream, plainOut);
|
||||
|
||||
decryptionStream.close();
|
||||
|
||||
OpenPgpMetadata decResult = decryptionStream.getResult();
|
||||
|
||||
assertEquals("", decResult.getFileName());
|
||||
JUtils.assertDateEquals(PGPLiteralData.NOW, decResult.getModificationDate());
|
||||
assertEquals(PGPLiteralData.BINARY, decResult.getFileEncoding().getCode());
|
||||
assertFalse(decResult.isForYourEyesOnly());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForYourEyesOnly() throws PGPException, IOException {
|
||||
ByteArrayInputStream dataIn = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
|
||||
ByteArrayOutputStream dataOut = new ByteArrayOutputStream();
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(dataOut)
|
||||
.withOptions(ProducerOptions.encrypt(
|
||||
EncryptionOptions
|
||||
.encryptCommunications()
|
||||
.addRecipient(certificate))
|
||||
.setForYourEyesOnly()
|
||||
);
|
||||
|
||||
Streams.pipeAll(dataIn, encryptionStream);
|
||||
encryptionStream.close();
|
||||
|
||||
EncryptionResult encResult = encryptionStream.getResult();
|
||||
assertEquals(PGPLiteralData.CONSOLE, encResult.getFileName());
|
||||
JUtils.assertDateEquals(PGPLiteralData.NOW, encResult.getModificationDate());
|
||||
assertEquals(PGPLiteralData.BINARY, encResult.getFileEncoding().getCode());
|
||||
assertTrue(encResult.isForYourEyesOnly());
|
||||
|
||||
ByteArrayInputStream cryptIn = new ByteArrayInputStream(dataOut.toByteArray());
|
||||
ByteArrayOutputStream plainOut = new ByteArrayOutputStream();
|
||||
|
||||
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(cryptIn)
|
||||
.withOptions(new ConsumerOptions()
|
||||
.addDecryptionKey(secretKey));
|
||||
Streams.pipeAll(decryptionStream, plainOut);
|
||||
|
||||
decryptionStream.close();
|
||||
|
||||
OpenPgpMetadata decResult = decryptionStream.getResult();
|
||||
|
||||
assertEquals(PGPLiteralData.CONSOLE, decResult.getFileName());
|
||||
JUtils.assertDateEquals(PGPLiteralData.NOW, decResult.getModificationDate());
|
||||
assertEquals(PGPLiteralData.BINARY, decResult.getFileEncoding().getCode());
|
||||
assertTrue(decResult.isForYourEyesOnly());
|
||||
}
|
||||
}
|
|
@ -1,59 +1,23 @@
|
|||
plugins {
|
||||
id 'application'
|
||||
id 'java'
|
||||
}
|
||||
|
||||
group 'org.pgpainless'
|
||||
version '0.2.1-SNAPSHOT'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation(project(":pgpainless-core"))
|
||||
implementation(project(":sop-java"))
|
||||
|
||||
implementation 'info.picocli:picocli:4.5.2'
|
||||
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
||||
|
||||
/*
|
||||
implementation "org.bouncycastle:bcprov-debug-jdk15on:$bouncyCastleVersion"
|
||||
/*/
|
||||
implementation "org.bouncycastle:bcprov-jdk15on:$bouncyCastleVersion"
|
||||
//*/
|
||||
implementation "org.bouncycastle:bcpg-jdk15on:$bouncyCastleVersion"
|
||||
|
||||
// https://mvnrepository.com/artifact/com.google.code.findbugs/jsr305
|
||||
implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
|
||||
}
|
||||
|
||||
mainClassName = 'org.pgpainless.sop.PGPainlessCLI'
|
||||
|
||||
def generatedVersionDir = "${buildDir}/generated-version"
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
output.dir(generatedVersionDir, builtBy: 'generateVersionProperties')
|
||||
}
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
task generateVersionProperties {
|
||||
doLast {
|
||||
def propertiesFile = file "$generatedVersionDir/version.properties"
|
||||
propertiesFile.parentFile.mkdirs()
|
||||
propertiesFile.createNewFile()
|
||||
// Instead of using a Properties object here, we directly write to the file
|
||||
// since Properties adds a timestamp, ruining reproducibility
|
||||
propertiesFile.write("version="+rootProject.version.toString())
|
||||
}
|
||||
}
|
||||
processResources.dependsOn generateVersionProperties
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes 'Main-Class': "$mainClassName"
|
||||
}
|
||||
|
||||
from {
|
||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
} {
|
||||
exclude "META-INF/*.SF"
|
||||
exclude "META-INF/*.DSA"
|
||||
exclude "META-INF/*.RSA"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PushbackInputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.util.ArmoredOutputStreamFactory;
|
||||
import sop.Ready;
|
||||
import sop.enums.ArmorLabel;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Armor;
|
||||
|
||||
public class ArmorImpl implements Armor {
|
||||
|
||||
public static final byte[] ARMOR_START = "-----BEGIN PGP".getBytes(Charset.forName("UTF8"));
|
||||
|
||||
boolean allowNested = false;
|
||||
|
||||
@Override
|
||||
public Armor label(ArmorLabel label) throws SOPGPException.UnsupportedOption {
|
||||
throw new SOPGPException.UnsupportedOption();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Armor allowNested() throws SOPGPException.UnsupportedOption {
|
||||
allowNested = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ready data(InputStream data) throws SOPGPException.BadData {
|
||||
return new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException {
|
||||
PushbackInputStream pbIn = new PushbackInputStream(data, ARMOR_START.length);
|
||||
byte[] buffer = new byte[ARMOR_START.length];
|
||||
int read = pbIn.read(buffer);
|
||||
pbIn.unread(buffer, 0, read);
|
||||
if (!allowNested && Arrays.equals(ARMOR_START, buffer)) {
|
||||
Streams.pipeAll(pbIn, System.out);
|
||||
} else {
|
||||
ArmoredOutputStream armor = ArmoredOutputStreamFactory.get(System.out);
|
||||
Streams.pipeAll(pbIn, armor);
|
||||
armor.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2021 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import sop.Ready;
|
||||
import sop.operation.Dearmor;
|
||||
|
||||
public class DearmorImpl implements Dearmor {
|
||||
|
||||
@Override
|
||||
public Ready data(InputStream data) throws IOException {
|
||||
InputStream decoder = PGPUtil.getDecoderStream(data);
|
||||
return new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException {
|
||||
Streams.pipeAll(decoder, outputStream);
|
||||
decoder.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
184
pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java
Normal file
184
pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java
Normal file
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* Copyright 2021 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.decryption_verification.ConsumerOptions;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.exception.NotYetImplementedException;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import sop.DecryptionResult;
|
||||
import sop.ReadyWithResult;
|
||||
import sop.SessionKey;
|
||||
import sop.Verification;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Decrypt;
|
||||
|
||||
public class DecryptImpl implements Decrypt {
|
||||
|
||||
private final ConsumerOptions consumerOptions = new ConsumerOptions();
|
||||
|
||||
@Override
|
||||
public DecryptImpl verifyNotBefore(Date timestamp) throws SOPGPException.UnsupportedOption {
|
||||
try {
|
||||
consumerOptions.verifyNotBefore(timestamp);
|
||||
} catch (NotYetImplementedException e) {
|
||||
// throw new SOPGPException.UnsupportedOption();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecryptImpl verifyNotAfter(Date timestamp) throws SOPGPException.UnsupportedOption {
|
||||
try {
|
||||
consumerOptions.verifyNotAfter(timestamp);
|
||||
} catch (NotYetImplementedException e) {
|
||||
// throw new SOPGPException.UnsupportedOption();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecryptImpl verifyWithCert(InputStream certIn) throws SOPGPException.BadData, IOException {
|
||||
try {
|
||||
PGPPublicKeyRingCollection certs = PGPainless.readKeyRing().keyRingCollection(certIn, false)
|
||||
.getPgpPublicKeyRingCollection();
|
||||
if (certs == null) {
|
||||
throw new SOPGPException.BadData(new PGPException("No certificates provided."));
|
||||
}
|
||||
|
||||
consumerOptions.addVerificationCerts(certs);
|
||||
|
||||
} catch (PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecryptImpl withSessionKey(SessionKey sessionKey) throws SOPGPException.UnsupportedOption {
|
||||
throw new SOPGPException.UnsupportedOption();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecryptImpl withPassword(String password) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption {
|
||||
consumerOptions.addDecryptionPassphrase(Passphrase.fromPassword(password));
|
||||
String withoutTrailingWhitespace = removeTrailingWhitespace(password);
|
||||
if (!password.equals(withoutTrailingWhitespace)) {
|
||||
consumerOptions.addDecryptionPassphrase(Passphrase.fromPassword(withoutTrailingWhitespace));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private static String removeTrailingWhitespace(String passphrase) {
|
||||
int i = passphrase.length() - 1;
|
||||
// Find index of first non-whitespace character from the back
|
||||
while (i > 0 && Character.isWhitespace(passphrase.charAt(i))) {
|
||||
i--;
|
||||
}
|
||||
return passphrase.substring(0, i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecryptImpl withKey(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo {
|
||||
try {
|
||||
PGPSecretKeyRingCollection secretKeys = PGPainless.readKeyRing()
|
||||
.keyRingCollection(keyIn, true)
|
||||
.getPGPSecretKeyRingCollection();
|
||||
|
||||
for (PGPSecretKeyRing secretKey : secretKeys) {
|
||||
KeyRingInfo info = new KeyRingInfo(secretKey);
|
||||
if (!info.isFullyDecrypted()) {
|
||||
throw new SOPGPException.KeyIsProtected();
|
||||
}
|
||||
}
|
||||
|
||||
consumerOptions.addDecryptionKeys(secretKeys, SecretKeyRingProtector.unprotectedKeys());
|
||||
} catch (IOException | PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadyWithResult<DecryptionResult> ciphertext(InputStream ciphertext)
|
||||
throws SOPGPException.BadData,
|
||||
SOPGPException.MissingArg {
|
||||
|
||||
if (consumerOptions.getDecryptionKeys().isEmpty() && consumerOptions.getDecryptionPassphrases().isEmpty()) {
|
||||
throw new SOPGPException.MissingArg("Missing decryption key or passphrase.");
|
||||
}
|
||||
|
||||
DecryptionStream decryptionStream;
|
||||
try {
|
||||
decryptionStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(ciphertext)
|
||||
.withOptions(consumerOptions);
|
||||
} catch (PGPException | IOException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
|
||||
return new ReadyWithResult<DecryptionResult>() {
|
||||
@Override
|
||||
public DecryptionResult writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature {
|
||||
Streams.pipeAll(decryptionStream, outputStream);
|
||||
decryptionStream.close();
|
||||
OpenPgpMetadata metadata = decryptionStream.getResult();
|
||||
|
||||
List<Verification> verificationList = new ArrayList<>();
|
||||
for (SubkeyIdentifier verifiedSigningKey : metadata.getVerifiedSignatures().keySet()) {
|
||||
PGPSignature signature = metadata.getVerifiedSignatures().get(verifiedSigningKey);
|
||||
Date verifyNotBefore = consumerOptions.getVerifyNotBefore();
|
||||
Date verifyNotAfter = consumerOptions.getVerifyNotAfter();
|
||||
|
||||
if (verifyNotAfter == null || !signature.getCreationTime().after(verifyNotAfter)) {
|
||||
if (verifyNotBefore == null || !signature.getCreationTime().before(verifyNotBefore)) {
|
||||
verificationList.add(new Verification(
|
||||
signature.getCreationTime(),
|
||||
verifiedSigningKey.getSubkeyFingerprint().toString(),
|
||||
verifiedSigningKey.getPrimaryKeyFingerprint().toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!consumerOptions.getCertificates().isEmpty()) {
|
||||
if (verificationList.isEmpty()) {
|
||||
throw new SOPGPException.NoSignature();
|
||||
}
|
||||
}
|
||||
|
||||
return new DecryptionResult(null, verificationList);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
140
pgpainless-sop/src/main/java/org/pgpainless/sop/EncryptImpl.java
Normal file
140
pgpainless-sop/src/main/java/org/pgpainless/sop/EncryptImpl.java
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright 2021 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.algorithm.StreamEncoding;
|
||||
import org.pgpainless.encryption_signing.EncryptionOptions;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.encryption_signing.ProducerOptions;
|
||||
import org.pgpainless.encryption_signing.SigningOptions;
|
||||
import org.pgpainless.exception.WrongPassphraseException;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import sop.util.ProxyOutputStream;
|
||||
import sop.Ready;
|
||||
import sop.enums.EncryptAs;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Encrypt;
|
||||
|
||||
public class EncryptImpl implements Encrypt {
|
||||
|
||||
EncryptionOptions encryptionOptions = new EncryptionOptions();
|
||||
SigningOptions signingOptions = null;
|
||||
|
||||
private EncryptAs encryptAs = EncryptAs.Binary;
|
||||
boolean armor = true;
|
||||
|
||||
@Override
|
||||
public Encrypt noArmor() {
|
||||
armor = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Encrypt mode(EncryptAs mode) throws SOPGPException.UnsupportedOption {
|
||||
this.encryptAs = mode;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Encrypt signWith(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.CertCannotSign, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData {
|
||||
try {
|
||||
PGPSecretKeyRingCollection keys = PGPainless.readKeyRing().secretKeyRingCollection(keyIn);
|
||||
|
||||
if (signingOptions == null) {
|
||||
signingOptions = SigningOptions.get();
|
||||
}
|
||||
try {
|
||||
signingOptions.addInlineSignatures(SecretKeyRingProtector.unprotectedKeys(), keys, DocumentSignatureType.BINARY_DOCUMENT);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new SOPGPException.CertCannotSign();
|
||||
} catch (WrongPassphraseException e) {
|
||||
throw new SOPGPException.KeyIsProtected();
|
||||
}
|
||||
} catch (IOException | PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Encrypt withPassword(String password) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption {
|
||||
encryptionOptions.addPassphrase(Passphrase.fromPassword(password));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Encrypt withCert(InputStream cert) throws SOPGPException.CertCannotEncrypt, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData {
|
||||
try {
|
||||
PGPPublicKeyRingCollection certificates = PGPainless.readKeyRing()
|
||||
.keyRingCollection(cert, false)
|
||||
.getPgpPublicKeyRingCollection();
|
||||
encryptionOptions.addRecipients(certificates);
|
||||
} catch (IOException | PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ready plaintext(InputStream plaintext) throws IOException {
|
||||
ProducerOptions producerOptions = signingOptions != null ?
|
||||
ProducerOptions.signAndEncrypt(encryptionOptions, signingOptions) :
|
||||
ProducerOptions.encrypt(encryptionOptions);
|
||||
producerOptions.setAsciiArmor(armor);
|
||||
producerOptions.setEncoding(encryptAsToStreamEncoding(encryptAs));
|
||||
|
||||
try {
|
||||
ProxyOutputStream proxy = new ProxyOutputStream();
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(proxy)
|
||||
.withOptions(producerOptions);
|
||||
|
||||
return new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException {
|
||||
proxy.replaceOutputStream(outputStream);
|
||||
Streams.pipeAll(plaintext, encryptionStream);
|
||||
encryptionStream.close();
|
||||
}
|
||||
};
|
||||
} catch (PGPException e) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
private static StreamEncoding encryptAsToStreamEncoding(EncryptAs encryptAs) {
|
||||
switch (encryptAs) {
|
||||
case Binary:
|
||||
return StreamEncoding.BINARY;
|
||||
case Text:
|
||||
return StreamEncoding.TEXT;
|
||||
case MIME:
|
||||
return StreamEncoding.UTF8;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid value encountered: " + encryptAs);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright 2021 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.key.util.KeyRingUtils;
|
||||
import org.pgpainless.util.ArmorUtils;
|
||||
import sop.operation.ExtractCert;
|
||||
import sop.Ready;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public class ExtractCertImpl implements ExtractCert {
|
||||
|
||||
private boolean armor = true;
|
||||
|
||||
@Override
|
||||
public ExtractCert noArmor() {
|
||||
armor = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ready key(InputStream keyInputStream) throws IOException, SOPGPException.BadData {
|
||||
try {
|
||||
PGPSecretKeyRing key = PGPainless.readKeyRing().secretKeyRing(keyInputStream);
|
||||
PGPPublicKeyRing cert = KeyRingUtils.publicKeyRingFrom(key);
|
||||
|
||||
return new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException {
|
||||
OutputStream out = armor ? ArmorUtils.createArmoredOutputStreamFor(cert, outputStream) : outputStream;
|
||||
cert.encode(out);
|
||||
|
||||
if (armor) {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright 2021 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditorInterface;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.util.ArmorUtils;
|
||||
import sop.Ready;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.GenerateKey;
|
||||
|
||||
public class GenerateKeyImpl implements GenerateKey {
|
||||
|
||||
private boolean armor = true;
|
||||
private final Set<String> userIds = new LinkedHashSet<>();
|
||||
|
||||
@Override
|
||||
public GenerateKey noArmor() {
|
||||
this.armor = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GenerateKey userId(String userId) {
|
||||
this.userIds.add(userId);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ready generate() throws SOPGPException.MissingArg, SOPGPException.UnsupportedAsymmetricAlgo, IOException {
|
||||
Iterator<String> userIdIterator = userIds.iterator();
|
||||
if (!userIdIterator.hasNext()) {
|
||||
throw new SOPGPException.MissingArg("Missing user-id.");
|
||||
}
|
||||
|
||||
PGPSecretKeyRing key;
|
||||
try {
|
||||
key = PGPainless.generateKeyRing()
|
||||
.modernKeyRing(userIdIterator.next(), null);
|
||||
|
||||
if (userIdIterator.hasNext()) {
|
||||
SecretKeyRingEditorInterface editor = PGPainless.modifyKeyRing(key);
|
||||
|
||||
while (userIdIterator.hasNext()) {
|
||||
editor.addUserId(userIdIterator.next(), SecretKeyRingProtector.unprotectedKeys());
|
||||
}
|
||||
|
||||
key = editor.done();
|
||||
}
|
||||
|
||||
PGPSecretKeyRing finalKey = key;
|
||||
return new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException {
|
||||
if (armor) {
|
||||
ArmoredOutputStream armoredOutputStream = ArmorUtils.toAsciiArmoredStream(finalKey, outputStream);
|
||||
finalKey.encode(armoredOutputStream);
|
||||
armoredOutputStream.close();
|
||||
} else {
|
||||
finalKey.encode(outputStream);
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
|
||||
throw new SOPGPException.UnsupportedAsymmetricAlgo(e);
|
||||
} catch (PGPException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.pgpainless.sop.commands.Armor;
|
||||
import org.pgpainless.sop.commands.Dearmor;
|
||||
import org.pgpainless.sop.commands.Decrypt;
|
||||
import org.pgpainless.sop.commands.Encrypt;
|
||||
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;
|
||||
|
||||
@CommandLine.Command(exitCodeOnInvalidInput = 69,
|
||||
subcommands = {
|
||||
Armor.class,
|
||||
Dearmor.class,
|
||||
Decrypt.class,
|
||||
Encrypt.class,
|
||||
ExtractCert.class,
|
||||
GenerateKey.class,
|
||||
Sign.class,
|
||||
Verify.class,
|
||||
Version.class
|
||||
}
|
||||
)
|
||||
public class PGPainlessCLI implements Runnable {
|
||||
|
||||
public PGPainlessCLI() {
|
||||
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
int code = new CommandLine(new PGPainlessCLI())
|
||||
.execute(args);
|
||||
System.exit(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.util.ArmorUtils;
|
||||
|
||||
public class Print {
|
||||
|
||||
public static String toString(PGPSecretKeyRing keyRing, boolean armor) throws IOException {
|
||||
if (armor) {
|
||||
return ArmorUtils.toAsciiArmoredString(keyRing);
|
||||
} else {
|
||||
return new String(keyRing.getEncoded(), "UTF-8");
|
||||
}
|
||||
}
|
||||
|
||||
public static String toString(PGPPublicKeyRing keyRing, boolean armor) throws IOException {
|
||||
if (armor) {
|
||||
return ArmorUtils.toAsciiArmoredString(keyRing);
|
||||
} else {
|
||||
return new String(keyRing.getEncoded(), "UTF-8");
|
||||
}
|
||||
}
|
||||
|
||||
public static String toString(byte[] bytes, boolean armor) throws IOException {
|
||||
if (armor) {
|
||||
return ArmorUtils.toAsciiArmoredString(bytes);
|
||||
} else {
|
||||
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
|
||||
}
|
||||
}
|
75
pgpainless-sop/src/main/java/org/pgpainless/sop/SOPImpl.java
Normal file
75
pgpainless-sop/src/main/java/org/pgpainless/sop/SOPImpl.java
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright 2021 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;
|
||||
|
||||
import sop.SOP;
|
||||
import sop.operation.Armor;
|
||||
import sop.operation.Dearmor;
|
||||
import sop.operation.Decrypt;
|
||||
import sop.operation.Encrypt;
|
||||
import sop.operation.ExtractCert;
|
||||
import sop.operation.GenerateKey;
|
||||
import sop.operation.Sign;
|
||||
import sop.operation.Verify;
|
||||
import sop.operation.Version;
|
||||
|
||||
public class SOPImpl implements SOP {
|
||||
|
||||
@Override
|
||||
public Version version() {
|
||||
return new VersionImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GenerateKey generateKey() {
|
||||
return new GenerateKeyImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtractCert extractCert() {
|
||||
return new ExtractCertImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sign sign() {
|
||||
return new SignImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Verify verify() {
|
||||
return new VerifyImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Encrypt encrypt() {
|
||||
return new EncryptImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Decrypt decrypt() {
|
||||
return new DecryptImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Armor armor() {
|
||||
return new ArmorImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dearmor dearmor() {
|
||||
return new DearmorImpl();
|
||||
}
|
||||
}
|
128
pgpainless-sop/src/main/java/org/pgpainless/sop/SignImpl.java
Normal file
128
pgpainless-sop/src/main/java/org/pgpainless/sop/SignImpl.java
Normal file
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright 2021 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;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.encryption_signing.EncryptionResult;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.encryption_signing.ProducerOptions;
|
||||
import org.pgpainless.encryption_signing.SigningOptions;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.util.ArmoredOutputStreamFactory;
|
||||
import sop.Ready;
|
||||
import sop.enums.SignAs;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Sign;
|
||||
|
||||
public class SignImpl implements Sign {
|
||||
|
||||
private boolean armor = true;
|
||||
private SignAs mode = SignAs.Binary;
|
||||
private List<PGPSecretKeyRing> keys = new ArrayList<>();
|
||||
private SigningOptions signingOptions = new SigningOptions();
|
||||
|
||||
@Override
|
||||
public Sign noArmor() {
|
||||
armor = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sign mode(SignAs mode) {
|
||||
this.mode = mode;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sign key(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException {
|
||||
try {
|
||||
PGPSecretKeyRing key = PGPainless.readKeyRing().secretKeyRing(keyIn);
|
||||
KeyRingInfo info = new KeyRingInfo(key);
|
||||
if (!info.isFullyDecrypted()) {
|
||||
throw new SOPGPException.KeyIsProtected();
|
||||
}
|
||||
signingOptions.addDetachedSignature(SecretKeyRingProtector.unprotectedKeys(), key, modeToSigType(mode));
|
||||
} catch (PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ready data(InputStream data) throws IOException {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
try {
|
||||
EncryptionStream signingStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(buffer)
|
||||
.withOptions(ProducerOptions.sign(signingOptions)
|
||||
.setAsciiArmor(armor));
|
||||
|
||||
return new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException {
|
||||
|
||||
if (signingStream.isClosed()) {
|
||||
throw new IllegalStateException("EncryptionStream is already closed.");
|
||||
}
|
||||
|
||||
Streams.pipeAll(data, signingStream);
|
||||
signingStream.close();
|
||||
EncryptionResult encryptionResult = signingStream.getResult();
|
||||
|
||||
List<PGPSignature> signatures = new ArrayList<>();
|
||||
for (SubkeyIdentifier key : encryptionResult.getDetachedSignatures().keySet()) {
|
||||
signatures.addAll(encryptionResult.getDetachedSignatures().get(key));
|
||||
}
|
||||
|
||||
OutputStream out;
|
||||
if (armor) {
|
||||
out = ArmoredOutputStreamFactory.get(outputStream);
|
||||
} else {
|
||||
out = outputStream;
|
||||
}
|
||||
for (PGPSignature sig : signatures) {
|
||||
sig.encode(out);
|
||||
}
|
||||
out.close();
|
||||
outputStream.close(); // armor out does not close underlying stream
|
||||
}
|
||||
};
|
||||
|
||||
} catch (PGPException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static DocumentSignatureType modeToSigType(SignAs mode) {
|
||||
return mode == SignAs.Binary ? DocumentSignatureType.BINARY_DOCUMENT
|
||||
: DocumentSignatureType.CANONICAL_TEXT_DOCUMENT;
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import static org.pgpainless.sop.Print.err_ln;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.PGPainless;
|
||||
|
||||
public class SopKeyUtil {
|
||||
|
||||
public static List<PGPSecretKeyRing> loadKeysFromFiles(File... files) throws IOException, PGPException {
|
||||
List<PGPSecretKeyRing> secretKeyRings = new ArrayList<>();
|
||||
for (File file : files) {
|
||||
try (FileInputStream in = new FileInputStream(file)) {
|
||||
secretKeyRings.add(PGPainless.readKeyRing().secretKeyRing(in));
|
||||
} catch (PGPException | IOException e) {
|
||||
err_ln("Could not load secret key " + file.getName() + ": " + e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return secretKeyRings;
|
||||
}
|
||||
|
||||
public static List<PGPPublicKeyRing> loadCertificatesFromFile(File... files) throws IOException {
|
||||
List<PGPPublicKeyRing> publicKeyRings = new ArrayList<>();
|
||||
for (File file : files) {
|
||||
try (FileInputStream in = new FileInputStream(file)) {
|
||||
PGPPublicKeyRingCollection collection = PGPainless.readKeyRing()
|
||||
.keyRingCollection(in, true)
|
||||
.getPgpPublicKeyRingCollection();
|
||||
if (collection == null) {
|
||||
throw new PGPException("Provided file " + file.getName() + " does not contain a certificate.");
|
||||
}
|
||||
for (PGPPublicKeyRing keyRing : collection) {
|
||||
publicKeyRings.add(keyRing);
|
||||
}
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("Could not read certificate from file " + file.getName() + ": " + e.getMessage());
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
return publicKeyRings;
|
||||
}
|
||||
}
|
124
pgpainless-sop/src/main/java/org/pgpainless/sop/VerifyImpl.java
Normal file
124
pgpainless-sop/src/main/java/org/pgpainless/sop/VerifyImpl.java
Normal file
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright 2021 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.decryption_verification.ConsumerOptions;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.exception.NotYetImplementedException;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import sop.Verification;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Verify;
|
||||
|
||||
public class VerifyImpl implements Verify {
|
||||
|
||||
ConsumerOptions options = new ConsumerOptions();
|
||||
|
||||
@Override
|
||||
public Verify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption {
|
||||
try {
|
||||
options.verifyNotBefore(timestamp);
|
||||
} catch (NotYetImplementedException e) {
|
||||
// throw new SOPGPException.UnsupportedOption();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Verify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption {
|
||||
try {
|
||||
options.verifyNotAfter(timestamp);
|
||||
} catch (NotYetImplementedException e) {
|
||||
// throw new SOPGPException.UnsupportedOption();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Verify cert(InputStream cert) throws SOPGPException.BadData {
|
||||
PGPPublicKeyRingCollection certificates;
|
||||
try {
|
||||
certificates = PGPainless.readKeyRing().publicKeyRingCollection(cert);
|
||||
} catch (IOException | PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
options.addVerificationCerts(certificates);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VerifyImpl signatures(InputStream signatures) throws SOPGPException.BadData {
|
||||
try {
|
||||
options.addVerificationOfDetachedSignatures(signatures);
|
||||
} catch (IOException | PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Verification> data(InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData {
|
||||
DecryptionStream decryptionStream;
|
||||
try {
|
||||
decryptionStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(data)
|
||||
.withOptions(options);
|
||||
|
||||
Streams.drain(decryptionStream);
|
||||
decryptionStream.close();
|
||||
|
||||
OpenPgpMetadata metadata = decryptionStream.getResult();
|
||||
List<Verification> verificationList = new ArrayList<>();
|
||||
|
||||
for (SubkeyIdentifier verifiedSigningKey : metadata.getVerifiedSignatures().keySet()) {
|
||||
PGPSignature signature = metadata.getVerifiedSignatures().get(verifiedSigningKey);
|
||||
Date verifyNotBefore = options.getVerifyNotBefore();
|
||||
Date verifyNotAfter = options.getVerifyNotAfter();
|
||||
|
||||
if (verifyNotAfter == null || !signature.getCreationTime().after(verifyNotAfter)) {
|
||||
if (verifyNotBefore == null || !signature.getCreationTime().before(verifyNotBefore)) {
|
||||
verificationList.add(new Verification(
|
||||
signature.getCreationTime(),
|
||||
verifiedSigningKey.getSubkeyFingerprint().toString(),
|
||||
verifiedSigningKey.getPrimaryKeyFingerprint().toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.getCertificates().isEmpty()) {
|
||||
if (verificationList.isEmpty()) {
|
||||
throw new SOPGPException.NoSignature();
|
||||
}
|
||||
}
|
||||
|
||||
return verificationList;
|
||||
} catch (PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2020 Paul Schaub.
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -13,21 +13,21 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
|
||||
import static org.pgpainless.sop.Print.print_ln;
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import sop.operation.Version;
|
||||
|
||||
@CommandLine.Command(name = "version", description = "Display version information about the tool",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class Version implements Runnable {
|
||||
public class VersionImpl implements Version {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "PGPainless-SOP";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
public String getVersion() {
|
||||
// See https://stackoverflow.com/a/50119235
|
||||
String version;
|
||||
try {
|
||||
|
@ -37,6 +37,6 @@ public class Version implements Runnable {
|
|||
} catch (IOException e) {
|
||||
version = "DEVELOPMENT";
|
||||
}
|
||||
print_ln("PGPainlessCLI " + version);
|
||||
return version;
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* 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 org.pgpainless.util.ArmoredOutputStreamFactory;
|
||||
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 to standard input",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
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, BEGIN_ARMOR.length);
|
||||
ArmoredOutputStream armoredOutputStream = ArmoredOutputStreamFactory.get(System.out)) {
|
||||
|
||||
// take a peek
|
||||
byte[] firstBytes = new byte[BEGIN_ARMOR.length];
|
||||
int readByteCount = pbIn.read(firstBytes);
|
||||
if (readByteCount != -1) {
|
||||
pbIn.unread(firstBytes, 0, readByteCount);
|
||||
}
|
||||
|
||||
if (Arrays.equals(BEGIN_ARMOR, firstBytes) && !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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,178 +0,0 @@
|
|||
/*
|
||||
* 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 static org.pgpainless.sop.Print.err_ln;
|
||||
import static org.pgpainless.sop.SopKeyUtil.loadKeysFromFiles;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.decryption_verification.ConsumerOptions;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.sop.SopKeyUtil;
|
||||
import picocli.CommandLine;
|
||||
|
||||
@CommandLine.Command(name = "decrypt",
|
||||
description = "Decrypt a message from standard input",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class Decrypt implements Runnable {
|
||||
|
||||
private static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
|
||||
|
||||
@CommandLine.Option(
|
||||
names = {"--session-key-out"},
|
||||
description = "Can be used to learn the session key on successful decryption",
|
||||
paramLabel = "SESSIONKEY")
|
||||
File sessionKeyOut;
|
||||
|
||||
@CommandLine.Option(
|
||||
names = {"--with-session-key"},
|
||||
description = "Enables decryption of the \"CIPHERTEXT\" using the session key directly against the \"SEIPD\" packet",
|
||||
paramLabel = "SESSIONKEY")
|
||||
File[] withSessionKey;
|
||||
|
||||
@CommandLine.Option(
|
||||
names = {"--with-password"},
|
||||
description = "Enables decryption based on any \"SKESK\" packets in the \"CIPHERTEXT\"",
|
||||
paramLabel = "PASSWORD")
|
||||
String[] withPassword;
|
||||
|
||||
@CommandLine.Option(names = {"--verify-out"},
|
||||
description = "Produces signature verification status to the designated file",
|
||||
paramLabel = "VERIFICATIONS")
|
||||
File verifyOut;
|
||||
|
||||
@CommandLine.Option(names = {"--verify-with"},
|
||||
description = "Certificates whose signatures would be acceptable for signatures over this message",
|
||||
paramLabel = "CERT")
|
||||
File[] certs;
|
||||
|
||||
@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 (\"-\").",
|
||||
paramLabel = "DATE")
|
||||
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.",
|
||||
paramLabel = "DATE")
|
||||
String notAfter = "now";
|
||||
|
||||
@CommandLine.Parameters(index = "0..*",
|
||||
description = "Secret keys to attempt decryption with",
|
||||
paramLabel = "KEY")
|
||||
File[] keys;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (verifyOut == null ^ certs == null) {
|
||||
err_ln("To enable signature verification, both --verify-out and at least one --verify-with argument must be supplied.");
|
||||
System.exit(23);
|
||||
}
|
||||
|
||||
if (sessionKeyOut != null || withSessionKey != null) {
|
||||
err_ln("session key in and out are not yet supported.");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
ConsumerOptions options = new ConsumerOptions();
|
||||
|
||||
List<PGPPublicKeyRing> verifyWith = null;
|
||||
try {
|
||||
|
||||
List<PGPSecretKeyRing> secretKeyRings = loadKeysFromFiles(keys);
|
||||
for (PGPSecretKeyRing secretKey : secretKeyRings) {
|
||||
options.addDecryptionKey(secretKey);
|
||||
}
|
||||
|
||||
if (certs != null) {
|
||||
verifyWith = SopKeyUtil.loadCertificatesFromFile(certs);
|
||||
for (PGPPublicKeyRing cert : verifyWith) {
|
||||
options.addVerificationCert(cert);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
DecryptionStream decryptionStream;
|
||||
try {
|
||||
decryptionStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(System.in)
|
||||
.withOptions(options);
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("Error constructing decryption stream: " + e.getMessage());
|
||||
System.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Streams.pipeAll(decryptionStream, System.out);
|
||||
System.out.flush();
|
||||
decryptionStream.close();
|
||||
} catch (IOException e) {
|
||||
err_ln("Unable to decrypt: " + e.getMessage());
|
||||
System.exit(29);
|
||||
}
|
||||
if (verifyOut == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
OpenPgpMetadata metadata = decryptionStream.getResult();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (verifyWith != null) {
|
||||
for (SubkeyIdentifier signingKey : metadata.getVerifiedSignatures().keySet()) {
|
||||
PGPSignature signature = metadata.getVerifiedSignatures().get(signingKey);
|
||||
sb.append(df.format(signature.getCreationTime())).append(' ')
|
||||
.append(signingKey.getSubkeyFingerprint()).append(' ')
|
||||
.append(signingKey.getPrimaryKeyFingerprint()).append('\n');
|
||||
}
|
||||
|
||||
try {
|
||||
verifyOut.createNewFile();
|
||||
PrintStream verifyPrinter = new PrintStream(new FileOutputStream(verifyOut));
|
||||
// CHECKSTYLE:OFF
|
||||
verifyPrinter.println(sb);
|
||||
// CHECKSTYLE:ON
|
||||
verifyPrinter.close();
|
||||
} catch (IOException e) {
|
||||
err_ln("Error writing verifications file: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
/*
|
||||
* 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 static org.pgpainless.sop.Print.err_ln;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.encryption_signing.EncryptionOptions;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.encryption_signing.ProducerOptions;
|
||||
import org.pgpainless.encryption_signing.SigningOptions;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.sop.SopKeyUtil;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import picocli.CommandLine;
|
||||
|
||||
@CommandLine.Command(name = "encrypt",
|
||||
description = "Encrypt a message from standard input",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class Encrypt implements Runnable {
|
||||
|
||||
public enum Type {
|
||||
binary,
|
||||
text,
|
||||
mime
|
||||
}
|
||||
|
||||
@CommandLine.Option(names = "--no-armor",
|
||||
description = "ASCII armor the output",
|
||||
negatable = true)
|
||||
boolean armor = true;
|
||||
|
||||
@CommandLine.Option(names = {"--as"},
|
||||
description = "Type of the input data. Defaults to 'binary'",
|
||||
paramLabel = "{binary|text|mime}")
|
||||
Type type;
|
||||
|
||||
@CommandLine.Option(names = "--with-password",
|
||||
description = "Encrypt the message with a password",
|
||||
paramLabel = "PASSWORD")
|
||||
String[] withPassword = new String[0];
|
||||
|
||||
@CommandLine.Option(names = "--sign-with",
|
||||
description = "Sign the output with a private key",
|
||||
paramLabel = "KEY")
|
||||
File[] signWith = new File[0];
|
||||
|
||||
@CommandLine.Parameters(description = "Certificates the message gets encrypted to",
|
||||
index = "0..*",
|
||||
paramLabel = "CERTS")
|
||||
File[] certs = new File[0];
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (certs.length == 0 && withPassword.length == 0) {
|
||||
err_ln("Please either provide --with-password or at least one CERT");
|
||||
System.exit(19);
|
||||
}
|
||||
|
||||
EncryptionOptions encOpt = new EncryptionOptions();
|
||||
SigningOptions signOpt = new SigningOptions();
|
||||
|
||||
try {
|
||||
List<PGPPublicKeyRing> encryptionKeys = SopKeyUtil.loadCertificatesFromFile(certs);
|
||||
for (PGPPublicKeyRing key : encryptionKeys) {
|
||||
encOpt.addRecipient(key);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
for (String s : withPassword) {
|
||||
Passphrase passphrase = Passphrase.fromPassword(s);
|
||||
encOpt.addPassphrase(passphrase);
|
||||
}
|
||||
|
||||
SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys();
|
||||
|
||||
for (int i = 0; i < signWith.length; i++) {
|
||||
try (FileInputStream fileIn = new FileInputStream(signWith[i])) {
|
||||
PGPSecretKeyRing secretKey = PGPainless.readKeyRing().secretKeyRing(fileIn);
|
||||
signOpt.addInlineSignature(protector, secretKey, parseType(type));
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("Cannot read secret key from file " + signWith[i].getName());
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(System.out)
|
||||
.withOptions(ProducerOptions
|
||||
.signAndEncrypt(encOpt, signOpt)
|
||||
.setAsciiArmor(armor));
|
||||
|
||||
Streams.pipeAll(System.in, encryptionStream);
|
||||
|
||||
encryptionStream.close();
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("An error happened.");
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static DocumentSignatureType parseType(Type type) {
|
||||
return type == Type.binary ? DocumentSignatureType.BINARY_DOCUMENT : DocumentSignatureType.CANONICAL_TEXT_DOCUMENT;
|
||||
}
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.KeyFlag;
|
||||
import org.pgpainless.key.generation.KeyRingBuilderInterface;
|
||||
import org.pgpainless.key.generation.KeySpec;
|
||||
import org.pgpainless.key.generation.type.KeyType;
|
||||
import org.pgpainless.key.generation.type.eddsa.EdDSACurve;
|
||||
import org.pgpainless.key.generation.type.xdh.XDHSpec;
|
||||
import org.pgpainless.sop.Print;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import static org.pgpainless.sop.Print.err_ln;
|
||||
import static org.pgpainless.sop.Print.print_ln;
|
||||
|
||||
@CommandLine.Command(name = "generate-key",
|
||||
description = "Generate a secret key",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class GenerateKey implements Runnable {
|
||||
|
||||
@CommandLine.Option(names = "--no-armor",
|
||||
description = "ASCII armor the output",
|
||||
negatable = true)
|
||||
boolean armor = true;
|
||||
|
||||
@CommandLine.Parameters(description = "User-ID, eg. \"Alice <alice@example.com>\"")
|
||||
List<String> userId;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (userId.isEmpty()) {
|
||||
print_ln("At least one user-id expected.");
|
||||
System.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
KeyRingBuilderInterface.WithAdditionalUserIdOrPassphrase builder = PGPainless.generateKeyRing()
|
||||
.withSubKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519))
|
||||
.withKeyFlags(KeyFlag.SIGN_DATA)
|
||||
.withDefaultAlgorithms())
|
||||
.withSubKey(KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519))
|
||||
.withKeyFlags(KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)
|
||||
.withDefaultAlgorithms())
|
||||
.withPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519))
|
||||
.withKeyFlags(KeyFlag.CERTIFY_OTHER)
|
||||
.withDefaultAlgorithms())
|
||||
.withPrimaryUserId(userId.get(0));
|
||||
|
||||
for (int i = 1; i < userId.size(); i++) {
|
||||
builder.withAdditionalUserId(userId.get(i));
|
||||
}
|
||||
|
||||
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
calendar.add(Calendar.YEAR, 3);
|
||||
Date expiration = calendar.getTime();
|
||||
|
||||
PGPSecretKeyRing secretKeys = builder.setExpirationDate(expiration)
|
||||
.withoutPassphrase()
|
||||
.build();
|
||||
|
||||
print_ln(Print.toString(secretKeys, armor));
|
||||
|
||||
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | PGPException | IOException e) {
|
||||
err_ln("Error creating OpenPGP key:");
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
* 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.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.encryption_signing.EncryptionResult;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.encryption_signing.ProducerOptions;
|
||||
import org.pgpainless.encryption_signing.SigningOptions;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.sop.Print;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.pgpainless.sop.Print.err_ln;
|
||||
import static org.pgpainless.sop.Print.print_ln;
|
||||
|
||||
@CommandLine.Command(name = "sign",
|
||||
description = "Create a detached signature on the data from standard input",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class Sign implements Runnable {
|
||||
|
||||
public enum Type {
|
||||
binary,
|
||||
text
|
||||
}
|
||||
|
||||
@CommandLine.Option(names = "--no-armor",
|
||||
description = "ASCII armor the output",
|
||||
negatable = true)
|
||||
boolean armor = true;
|
||||
|
||||
@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;
|
||||
|
||||
@CommandLine.Parameters(description = "Secret keys used for signing",
|
||||
paramLabel = "KEY",
|
||||
arity = "1..*")
|
||||
File[] secretKeyFile;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
PGPSecretKeyRing[] secretKeys = new PGPSecretKeyRing[secretKeyFile.length];
|
||||
for (int i = 0, secretKeyFileLength = secretKeyFile.length; i < secretKeyFileLength; i++) {
|
||||
File file = secretKeyFile[i];
|
||||
try {
|
||||
PGPSecretKeyRing secretKey = PGPainless.readKeyRing().secretKeyRing(new FileInputStream(file));
|
||||
secretKeys[i] = secretKey;
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("Error reading secret key ring " + file.getName());
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
SigningOptions signOpt = new SigningOptions();
|
||||
for (PGPSecretKeyRing signingKey : secretKeys) {
|
||||
signOpt.addDetachedSignature(SecretKeyRingProtector.unprotectedKeys(), signingKey,
|
||||
type == Type.text ? DocumentSignatureType.CANONICAL_TEXT_DOCUMENT : DocumentSignatureType.BINARY_DOCUMENT);
|
||||
}
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(out)
|
||||
.withOptions(ProducerOptions
|
||||
.sign(signOpt)
|
||||
.setAsciiArmor(armor));
|
||||
|
||||
Streams.pipeAll(System.in, encryptionStream);
|
||||
encryptionStream.close();
|
||||
|
||||
EncryptionResult result = encryptionStream.getResult();
|
||||
for (SubkeyIdentifier signingKey : result.getDetachedSignatures().keySet()) {
|
||||
for (PGPSignature signature : result.getDetachedSignatures().get(signingKey)) {
|
||||
print_ln(Print.toString(signature.getEncoded(), armor));
|
||||
}
|
||||
}
|
||||
} catch (PGPException | IOException e) {
|
||||
err_ln("Error signing data.");
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,205 +0,0 @@
|
|||
/*
|
||||
* 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 static org.pgpainless.sop.Print.err_ln;
|
||||
import static org.pgpainless.sop.Print.print_ln;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.decryption_verification.ConsumerOptions;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import picocli.CommandLine;
|
||||
|
||||
@CommandLine.Command(name = "verify",
|
||||
description = "Verify a detached signature over the data from standard input",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
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",
|
||||
paramLabel = "SIGNATURE")
|
||||
File signature;
|
||||
|
||||
@CommandLine.Parameters(index = "1..*",
|
||||
arity = "1..*",
|
||||
description = "Public key certificates",
|
||||
paramLabel = "CERT")
|
||||
File[] certificates;
|
||||
|
||||
@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 (\"-\").",
|
||||
paramLabel = "DATE")
|
||||
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.",
|
||||
paramLabel = "DATE")
|
||||
String notAfter = "now";
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Date notBeforeDate = parseNotBefore();
|
||||
Date notAfterDate = parseNotAfter();
|
||||
|
||||
ConsumerOptions options = new ConsumerOptions();
|
||||
try (FileInputStream sigIn = new FileInputStream(signature)) {
|
||||
options.addVerificationOfDetachedSignatures(sigIn);
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("Cannot read detached signature: " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
Map<PGPPublicKeyRing, File> publicKeys = readCertificatesFromFiles();
|
||||
if (publicKeys.isEmpty()) {
|
||||
err_ln("No certificates supplied.");
|
||||
System.exit(19);
|
||||
}
|
||||
|
||||
for (PGPPublicKeyRing cert : publicKeys.keySet()) {
|
||||
options.addVerificationCert(cert);
|
||||
}
|
||||
|
||||
OpenPgpMetadata metadata;
|
||||
try {
|
||||
DecryptionStream verifier = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(System.in)
|
||||
.withOptions(options);
|
||||
|
||||
OutputStream out = new NullOutputStream();
|
||||
Streams.pipeAll(verifier, out);
|
||||
verifier.close();
|
||||
|
||||
metadata = verifier.getResult();
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("Signature validation failed.");
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
Map<SubkeyIdentifier, PGPSignature> signaturesInTimeRange = new HashMap<>();
|
||||
for (SubkeyIdentifier signingKey : metadata.getVerifiedSignatures().keySet()) {
|
||||
PGPSignature signature = metadata.getVerifiedSignatures().get(signingKey);
|
||||
Date creationTime = signature.getCreationTime();
|
||||
if (!creationTime.before(notBeforeDate) && !creationTime.after(notAfterDate)) {
|
||||
signaturesInTimeRange.put(signingKey, signature);
|
||||
}
|
||||
}
|
||||
|
||||
if (signaturesInTimeRange.isEmpty()) {
|
||||
err_ln("No valid signatures found.");
|
||||
System.exit(3);
|
||||
}
|
||||
|
||||
printValidSignatures(signaturesInTimeRange, publicKeys);
|
||||
}
|
||||
|
||||
private void printValidSignatures(Map<SubkeyIdentifier, PGPSignature> validSignatures, Map<PGPPublicKeyRing, File> publicKeys) {
|
||||
for (SubkeyIdentifier signingKey : validSignatures.keySet()) {
|
||||
PGPSignature signature = validSignatures.get(signingKey);
|
||||
|
||||
for (PGPPublicKeyRing ring : publicKeys.keySet()) {
|
||||
// Search signing key ring
|
||||
File file = publicKeys.get(ring);
|
||||
if (ring.getPublicKey(signingKey.getKeyId()) == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String utcSigDate = df.format(signature.getCreationTime());
|
||||
print_ln(utcSigDate + " " + signingKey.getSubkeyFingerprint() + " " + signingKey.getPrimaryKeyFingerprint() +
|
||||
" signed by " + file.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<PGPPublicKeyRing, File> readCertificatesFromFiles() {
|
||||
Map<PGPPublicKeyRing, File> publicKeys = new HashMap<>();
|
||||
for (File cert : certificates) {
|
||||
try (FileInputStream in = new FileInputStream(cert)) {
|
||||
PGPPublicKeyRingCollection collection = PGPainless.readKeyRing().publicKeyRingCollection(in);
|
||||
for (PGPPublicKeyRing ring : collection) {
|
||||
publicKeys.put(ring, cert);
|
||||
}
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("Cannot read certificate from file " + cert.getAbsolutePath() + ":");
|
||||
err_ln(e.getMessage());
|
||||
}
|
||||
}
|
||||
return publicKeys;
|
||||
}
|
||||
|
||||
private Date parseNotAfter() {
|
||||
try {
|
||||
return notAfter.equals("now") ? new Date() : notAfter.equals("-") ? endOfTime : df.parse(notAfter);
|
||||
} catch (ParseException e) {
|
||||
err_ln("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) {
|
||||
err_ln("Invalid date string supplied as value of --not-before.");
|
||||
System.exit(1);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class NullOutputStream extends OutputStream {
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
// Nope
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2020 Paul Schaub.
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,9 +14,6 @@
|
|||
* 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>
|
||||
* Implementation of the java-sop package using pgpainless-core.
|
||||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
|
|
@ -15,13 +15,14 @@
|
|||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import picocli.CommandLine;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class DummyTest {
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class VersionTest {
|
||||
|
||||
@Test
|
||||
public void dummyTest() {
|
||||
new CommandLine(new PGPainlessCLI()).execute("generate-key", "Ed Snowden <citizen4@lavabit.com>");
|
||||
public void assertNameEqualsPGPainless() {
|
||||
assertEquals("PGPainless-SOP", new SOPImpl().version().getName());
|
||||
}
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
rootProject.name = 'PGPainless'
|
||||
|
||||
include 'pgpainless-core',
|
||||
'pgpainless-sop'
|
||||
'sop-java',
|
||||
'pgpainless-sop',
|
||||
'sop-java-picocli',
|
||||
'pgpainless-cli'
|
||||
|
||||
|
|
37
sop-java-picocli/build.gradle
Normal file
37
sop-java-picocli/build.gradle
Normal file
|
@ -0,0 +1,37 @@
|
|||
plugins {
|
||||
id 'application'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":sop-java"))
|
||||
implementation 'info.picocli:picocli:4.6.1'
|
||||
implementation 'com.google.inject:guice:5.0.1'
|
||||
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
||||
|
||||
// https://todd.ginsberg.com/post/testing-system-exit/
|
||||
testImplementation 'com.ginsberg:junit5-system-exit:1.1.1'
|
||||
|
||||
testImplementation "org.mockito:mockito-core:3.11.2"
|
||||
|
||||
// https://mvnrepository.com/artifact/com.google.code.findbugs/jsr305
|
||||
implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
|
||||
}
|
||||
|
||||
mainClassName = 'sop.cli.picocli.SopCLI'
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes 'Main-Class': "$mainClassName"
|
||||
}
|
||||
|
||||
from {
|
||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
} {
|
||||
exclude "META-INF/*.SF"
|
||||
exclude "META-INF/*.DSA"
|
||||
exclude "META-INF/*.RSA"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.cli.picocli;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import sop.util.UTCUtil;
|
||||
|
||||
public class DateParser {
|
||||
|
||||
public static final Date BEGINNING_OF_TIME = new Date(0);
|
||||
public static final Date END_OF_TIME = new Date(8640000000000000L);
|
||||
|
||||
public static Date parseNotAfter(String notAfter) {
|
||||
Date date = notAfter.equals("now") ? new Date() : notAfter.equals("-") ? END_OF_TIME : UTCUtil.parseUTCDate(notAfter);
|
||||
if (date == null) {
|
||||
Print.errln("Invalid date string supplied as value of --not-after.");
|
||||
System.exit(1);
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
public static Date parseNotBefore(String notBefore) {
|
||||
Date date = notBefore.equals("now") ? new Date() : notBefore.equals("-") ? BEGINNING_OF_TIME : UTCUtil.parseUTCDate(notBefore);
|
||||
if (date == null) {
|
||||
Print.errln("Invalid date string supplied as value of --not-before.");
|
||||
System.exit(1);
|
||||
}
|
||||
return date;
|
||||
}
|
||||
}
|
37
sop-java-picocli/src/main/java/sop/cli/picocli/Print.java
Normal file
37
sop-java-picocli/src/main/java/sop/cli/picocli/Print.java
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.cli.picocli;
|
||||
|
||||
public class Print {
|
||||
|
||||
public static void errln(String string) {
|
||||
// CHECKSTYLE:OFF
|
||||
System.err.println(string);
|
||||
// CHECKSTYLE:ON
|
||||
}
|
||||
|
||||
public static void trace(Throwable e) {
|
||||
// CHECKSTYLE:OFF
|
||||
e.printStackTrace();
|
||||
// CHECKSTYLE:ON
|
||||
}
|
||||
|
||||
public static void outln(String string) {
|
||||
// CHECKSTYLE:OFF
|
||||
System.out.println(string);
|
||||
// CHECKSTYLE:ON
|
||||
}
|
||||
}
|
71
sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java
Normal file
71
sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.cli.picocli;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import sop.SOP;
|
||||
import sop.cli.picocli.commands.ArmorCmd;
|
||||
import sop.cli.picocli.commands.DearmorCmd;
|
||||
import sop.cli.picocli.commands.DecryptCmd;
|
||||
import sop.cli.picocli.commands.EncryptCmd;
|
||||
import sop.cli.picocli.commands.ExtractCertCmd;
|
||||
import sop.cli.picocli.commands.GenerateKeyCmd;
|
||||
import sop.cli.picocli.commands.SignCmd;
|
||||
import sop.cli.picocli.commands.VerifyCmd;
|
||||
import sop.cli.picocli.commands.VersionCmd;
|
||||
|
||||
@CommandLine.Command(
|
||||
exitCodeOnInvalidInput = 69,
|
||||
subcommands = {
|
||||
ArmorCmd.class,
|
||||
DearmorCmd.class,
|
||||
DecryptCmd.class,
|
||||
EncryptCmd.class,
|
||||
ExtractCertCmd.class,
|
||||
GenerateKeyCmd.class,
|
||||
SignCmd.class,
|
||||
VerifyCmd.class,
|
||||
VersionCmd.class
|
||||
}
|
||||
)
|
||||
public class SopCLI {
|
||||
|
||||
static SOP SOP_INSTANCE;
|
||||
|
||||
public static void main(String[] args) {
|
||||
int exitCode = execute(args);
|
||||
if (exitCode != 0) {
|
||||
System.exit(exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
public static int execute(String[] args) {
|
||||
return new CommandLine(SopCLI.class)
|
||||
.setCaseInsensitiveEnumValuesAllowed(true)
|
||||
.execute(args);
|
||||
}
|
||||
|
||||
public static SOP getSop() {
|
||||
if (SOP_INSTANCE == null) {
|
||||
throw new IllegalStateException("No SOP backend set.");
|
||||
}
|
||||
return SOP_INSTANCE;
|
||||
}
|
||||
|
||||
public static void setSopInstance(SOP instance) {
|
||||
SOP_INSTANCE = instance;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.cli.picocli.commands;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import sop.Ready;
|
||||
import sop.cli.picocli.Print;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
import sop.enums.ArmorLabel;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Armor;
|
||||
|
||||
@CommandLine.Command(name = "armor",
|
||||
description = "Add ASCII Armor to standard input",
|
||||
exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE)
|
||||
public class ArmorCmd implements Runnable {
|
||||
|
||||
@CommandLine.Option(names = {"--label"}, description = "Label to be used in the header and tail of the armoring.", paramLabel = "{auto|sig|key|cert|message}")
|
||||
ArmorLabel label;
|
||||
|
||||
@CommandLine.Option(names = {"--allow-nested"}, description = "Allow additional armoring of already armored input")
|
||||
boolean allowNested = false;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Armor armor = SopCLI.getSop().armor();
|
||||
if (allowNested) {
|
||||
try {
|
||||
armor.allowNested();
|
||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
||||
Print.errln("Option --allow-nested is not supported.");
|
||||
Print.trace(unsupportedOption);
|
||||
System.exit(unsupportedOption.getExitCode());
|
||||
}
|
||||
}
|
||||
if (label != null) {
|
||||
try {
|
||||
armor.label(label);
|
||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
||||
Print.errln("Armor labels not supported.");
|
||||
System.exit(unsupportedOption.getExitCode());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Ready ready = armor.data(System.in);
|
||||
ready.writeTo(System.out);
|
||||
} catch (SOPGPException.BadData badData) {
|
||||
Print.errln("Bad data.");
|
||||
Print.trace(badData);
|
||||
System.exit(badData.getExitCode());
|
||||
} catch (IOException e) {
|
||||
Print.errln("IO Error.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,28 +13,34 @@
|
|||
* 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;
|
||||
package sop.cli.picocli.commands;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.pgpainless.sop.Print.err_ln;
|
||||
import picocli.CommandLine;
|
||||
import sop.cli.picocli.Print;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
@CommandLine.Command(name = "dearmor",
|
||||
description = "Remove ASCII Armor from standard input",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class Dearmor implements Runnable {
|
||||
exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE)
|
||||
public class DearmorCmd implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try (ArmoredInputStream in = new ArmoredInputStream(System.in, true)) {
|
||||
Streams.pipeAll(in, System.out);
|
||||
try {
|
||||
SopCLI.getSop()
|
||||
.dearmor()
|
||||
.data(System.in)
|
||||
.writeTo(System.out);
|
||||
} catch (SOPGPException.BadData e) {
|
||||
Print.errln("Bad data.");
|
||||
Print.trace(e);
|
||||
System.exit(e.getExitCode());
|
||||
} catch (IOException e) {
|
||||
err_ln("Data cannot be dearmored.");
|
||||
err_ln(e.getMessage());
|
||||
Print.errln("IO Error.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,290 @@
|
|||
/*
|
||||
* 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 sop.cli.picocli.commands;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import sop.DecryptionResult;
|
||||
import sop.ReadyWithResult;
|
||||
import sop.SessionKey;
|
||||
import sop.Verification;
|
||||
import sop.cli.picocli.DateParser;
|
||||
import sop.cli.picocli.Print;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Decrypt;
|
||||
import sop.util.HexUtil;
|
||||
|
||||
@CommandLine.Command(name = "decrypt",
|
||||
description = "Decrypt a message from standard input",
|
||||
exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE)
|
||||
public class DecryptCmd implements Runnable {
|
||||
|
||||
@CommandLine.Option(
|
||||
names = {"--session-key-out"},
|
||||
description = "Can be used to learn the session key on successful decryption",
|
||||
paramLabel = "SESSIONKEY")
|
||||
File sessionKeyOut;
|
||||
|
||||
@CommandLine.Option(
|
||||
names = {"--with-session-key"},
|
||||
description = "Enables decryption of the \"CIPHERTEXT\" using the session key directly against the \"SEIPD\" packet",
|
||||
paramLabel = "SESSIONKEY")
|
||||
List<String> withSessionKey = new ArrayList<>();
|
||||
|
||||
@CommandLine.Option(
|
||||
names = {"--with-password"},
|
||||
description = "Enables decryption based on any \"SKESK\" packets in the \"CIPHERTEXT\"",
|
||||
paramLabel = "PASSWORD")
|
||||
List<String> withPassword = new ArrayList<>();
|
||||
|
||||
@CommandLine.Option(names = {"--verify-out"},
|
||||
description = "Produces signature verification status to the designated file",
|
||||
paramLabel = "VERIFICATIONS")
|
||||
File verifyOut;
|
||||
|
||||
@CommandLine.Option(names = {"--verify-with"},
|
||||
description = "Certificates whose signatures would be acceptable for signatures over this message",
|
||||
paramLabel = "CERT")
|
||||
List<File> certs = new ArrayList<>();
|
||||
|
||||
@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 (\"-\").",
|
||||
paramLabel = "DATE")
|
||||
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.",
|
||||
paramLabel = "DATE")
|
||||
String notAfter = "now";
|
||||
|
||||
@CommandLine.Parameters(index = "0..*",
|
||||
description = "Secret keys to attempt decryption with",
|
||||
paramLabel = "KEY")
|
||||
List<File> keys = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
unlinkExistingVerifyOut(verifyOut);
|
||||
|
||||
Decrypt decrypt = SopCLI.getSop().decrypt();
|
||||
setNotAfter(notAfter, decrypt);
|
||||
setNotBefore(notBefore, decrypt);
|
||||
setWithPasswords(withPassword, decrypt);
|
||||
setWithSessionKeys(withSessionKey, decrypt);
|
||||
setVerifyWith(certs, decrypt);
|
||||
setDecryptWith(keys, decrypt);
|
||||
|
||||
if (verifyOut != null && certs.isEmpty()) {
|
||||
Print.errln("--verify-out is requested, but no --verify-with was provided.");
|
||||
System.exit(23);
|
||||
}
|
||||
|
||||
try {
|
||||
ReadyWithResult<DecryptionResult> ready = decrypt.ciphertext(System.in);
|
||||
DecryptionResult result = ready.writeTo(System.out);
|
||||
if (sessionKeyOut != null) {
|
||||
if (sessionKeyOut.exists()) {
|
||||
Print.errln("File " + sessionKeyOut.getAbsolutePath() + " already exists.");
|
||||
Print.trace(new SOPGPException.OutputExists());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
try (FileOutputStream outputStream = new FileOutputStream(sessionKeyOut)) {
|
||||
if (!result.getSessionKey().isPresent()) {
|
||||
Print.errln("Session key not extracted. Possibly the feature is not supported.");
|
||||
System.exit(SOPGPException.UnsupportedOption.EXIT_CODE);
|
||||
} else {
|
||||
SessionKey sessionKey = result.getSessionKey().get();
|
||||
outputStream.write(sessionKey.getAlgorithm());
|
||||
outputStream.write(sessionKey.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (verifyOut != null) {
|
||||
if (!verifyOut.createNewFile()) {
|
||||
throw new IOException("Cannot create file " + verifyOut.getAbsolutePath());
|
||||
}
|
||||
try (FileOutputStream outputStream = new FileOutputStream(verifyOut)) {
|
||||
PrintWriter writer = new PrintWriter(outputStream);
|
||||
for (Verification verification : result.getVerifications()) {
|
||||
// CHECKSTYLE:OFF
|
||||
writer.println(verification.toString());
|
||||
// CHECKSTYLE:ON
|
||||
}
|
||||
writer.flush();
|
||||
}
|
||||
}
|
||||
} catch (SOPGPException.BadData badData) {
|
||||
Print.errln("No valid OpenPGP message found on Standard Input.");
|
||||
Print.trace(badData);
|
||||
System.exit(badData.getExitCode());
|
||||
} catch (SOPGPException.MissingArg missingArg) {
|
||||
Print.errln("Missing arguments.");
|
||||
Print.trace(missingArg);
|
||||
System.exit(missingArg.getExitCode());
|
||||
} catch (IOException e) {
|
||||
Print.errln("IO Error.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
} catch (SOPGPException.NoSignature noSignature) {
|
||||
Print.errln("No verifiable signature found.");
|
||||
Print.trace(noSignature);
|
||||
System.exit(noSignature.getExitCode());
|
||||
} catch (SOPGPException.CannotDecrypt cannotDecrypt) {
|
||||
Print.errln("Cannot decrypt.");
|
||||
Print.trace(cannotDecrypt);
|
||||
System.exit(cannotDecrypt.getExitCode());
|
||||
}
|
||||
}
|
||||
|
||||
private void setDecryptWith(List<File> keys, Decrypt decrypt) {
|
||||
for (File key : keys) {
|
||||
try (FileInputStream keyIn = new FileInputStream(key)) {
|
||||
decrypt.withKey(keyIn);
|
||||
} catch (SOPGPException.KeyIsProtected keyIsProtected) {
|
||||
Print.errln("Key in file " + key.getAbsolutePath() + " is password protected.");
|
||||
Print.trace(keyIsProtected);
|
||||
System.exit(1);
|
||||
} catch (SOPGPException.UnsupportedAsymmetricAlgo unsupportedAsymmetricAlgo) {
|
||||
Print.errln("Key uses unsupported asymmetric algorithm.");
|
||||
Print.trace(unsupportedAsymmetricAlgo);
|
||||
System.exit(unsupportedAsymmetricAlgo.getExitCode());
|
||||
} catch (SOPGPException.BadData badData) {
|
||||
Print.errln("File " + key.getAbsolutePath() + " does not contain a private key.");
|
||||
Print.trace(badData);
|
||||
System.exit(badData.getExitCode());
|
||||
} catch (FileNotFoundException e) {
|
||||
Print.errln("File " + key.getAbsolutePath() + " does not exist.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
} catch (IOException e) {
|
||||
Print.errln("IO Error.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setVerifyWith(List<File> certs, Decrypt decrypt) {
|
||||
for (File cert : certs) {
|
||||
try (FileInputStream certIn = new FileInputStream(cert)) {
|
||||
decrypt.verifyWithCert(certIn);
|
||||
} catch (FileNotFoundException e) {
|
||||
Print.errln("File " + cert.getAbsolutePath() + " does not exist.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
} catch (IOException e) {
|
||||
Print.errln("IO Error.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
} catch (SOPGPException.BadData badData) {
|
||||
Print.errln("File " + cert.getAbsolutePath() + " does not contain a valid certificate.");
|
||||
Print.trace(badData);
|
||||
System.exit(badData.getExitCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void unlinkExistingVerifyOut(File verifyOut) {
|
||||
if (verifyOut == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (verifyOut.exists()) {
|
||||
if (!verifyOut.delete()) {
|
||||
Print.errln("Cannot delete existing verification file" + verifyOut.getAbsolutePath());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setWithSessionKeys(List<String> withSessionKey, Decrypt decrypt) {
|
||||
Pattern sessionKeyPattern = Pattern.compile("^\\d+:[0-9A-F]+$");
|
||||
for (String sessionKey : withSessionKey) {
|
||||
if (!sessionKeyPattern.matcher(sessionKey).matches()) {
|
||||
Print.errln("Invalid session key format.");
|
||||
Print.errln("Session keys are expected in the format 'ALGONUM:HEXKEY'");
|
||||
System.exit(1);
|
||||
}
|
||||
String[] split = sessionKey.split(":");
|
||||
byte algorithm = (byte) Integer.parseInt(split[0]);
|
||||
byte[] key = HexUtil.hexToBytes(split[1]);
|
||||
|
||||
try {
|
||||
decrypt.withSessionKey(new SessionKey(algorithm, key));
|
||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
||||
Print.errln("Unsupported option '--with-session-key'.");
|
||||
Print.trace(unsupportedOption);
|
||||
System.exit(unsupportedOption.getExitCode());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setWithPasswords(List<String> withPassword, Decrypt decrypt) {
|
||||
for (String password : withPassword) {
|
||||
try {
|
||||
decrypt.withPassword(password);
|
||||
} catch (SOPGPException.PasswordNotHumanReadable passwordNotHumanReadable) {
|
||||
Print.errln("Password not human readable.");
|
||||
Print.trace(passwordNotHumanReadable);
|
||||
System.exit(passwordNotHumanReadable.getExitCode());
|
||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
||||
Print.errln("Unsupported option '--with-password'.");
|
||||
Print.trace(unsupportedOption);
|
||||
System.exit(unsupportedOption.getExitCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setNotAfter(String notAfter, Decrypt decrypt) {
|
||||
Date notAfterDate = DateParser.parseNotAfter(notAfter);
|
||||
try {
|
||||
decrypt.verifyNotAfter(notAfterDate);
|
||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
||||
Print.errln("Option '--not-after' not supported.");
|
||||
Print.trace(unsupportedOption);
|
||||
System.exit(unsupportedOption.getExitCode());
|
||||
}
|
||||
}
|
||||
|
||||
private void setNotBefore(String notBefore, Decrypt decrypt) {
|
||||
Date notBeforeDate = DateParser.parseNotBefore(notBefore);
|
||||
try {
|
||||
decrypt.verifyNotBefore(notBeforeDate);
|
||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
||||
Print.errln("Option '--not-before' not supported.");
|
||||
Print.trace(unsupportedOption);
|
||||
System.exit(unsupportedOption.getExitCode());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* 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 sop.cli.picocli.commands;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import sop.Ready;
|
||||
import sop.cli.picocli.Print;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
import sop.enums.EncryptAs;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Encrypt;
|
||||
|
||||
@CommandLine.Command(name = "encrypt",
|
||||
description = "Encrypt a message from standard input",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class EncryptCmd implements Runnable {
|
||||
|
||||
@CommandLine.Option(names = "--no-armor",
|
||||
description = "ASCII armor the output",
|
||||
negatable = true)
|
||||
boolean armor = true;
|
||||
|
||||
@CommandLine.Option(names = {"--as"},
|
||||
description = "Type of the input data. Defaults to 'binary'",
|
||||
paramLabel = "{binary|text|mime}")
|
||||
EncryptAs type;
|
||||
|
||||
@CommandLine.Option(names = "--with-password",
|
||||
description = "Encrypt the message with a password",
|
||||
paramLabel = "PASSWORD")
|
||||
List<String> withPassword = new ArrayList<>();
|
||||
|
||||
@CommandLine.Option(names = "--sign-with",
|
||||
description = "Sign the output with a private key",
|
||||
paramLabel = "KEY")
|
||||
List<File> signWith = new ArrayList<>();
|
||||
|
||||
@CommandLine.Parameters(description = "Certificates the message gets encrypted to",
|
||||
index = "0..*",
|
||||
paramLabel = "CERTS")
|
||||
List<File> certs = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Encrypt encrypt = SopCLI.getSop().encrypt();
|
||||
if (type != null) {
|
||||
try {
|
||||
encrypt.mode(type);
|
||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
||||
Print.errln("Unsupported option '--as'.");
|
||||
Print.trace(unsupportedOption);
|
||||
System.exit(unsupportedOption.getExitCode());
|
||||
}
|
||||
}
|
||||
|
||||
if (withPassword.isEmpty() && certs.isEmpty()) {
|
||||
Print.errln("At least one password or cert file required for encryption.");
|
||||
System.exit(19);
|
||||
}
|
||||
|
||||
for (String password : withPassword) {
|
||||
try {
|
||||
encrypt.withPassword(password);
|
||||
} catch (SOPGPException.PasswordNotHumanReadable passwordNotHumanReadable) {
|
||||
Print.errln("Password is not human-readable.");
|
||||
Print.trace(passwordNotHumanReadable);
|
||||
System.exit(passwordNotHumanReadable.getExitCode());
|
||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
||||
Print.errln("Unsupported option '--with-password'.");
|
||||
Print.trace(unsupportedOption);
|
||||
System.exit(unsupportedOption.getExitCode());
|
||||
}
|
||||
}
|
||||
|
||||
for (File keyFile : signWith) {
|
||||
try (FileInputStream keyIn = new FileInputStream(keyFile)) {
|
||||
encrypt.signWith(keyIn);
|
||||
} catch (FileNotFoundException e) {
|
||||
Print.errln("Key file " + keyFile.getAbsolutePath() + " not found.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
} catch (IOException e) {
|
||||
Print.errln("IO Error.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
} catch (SOPGPException.KeyIsProtected keyIsProtected) {
|
||||
Print.errln("Key from " + keyFile.getAbsolutePath() + " is password protected.");
|
||||
Print.trace(keyIsProtected);
|
||||
System.exit(1);
|
||||
} catch (SOPGPException.UnsupportedAsymmetricAlgo unsupportedAsymmetricAlgo) {
|
||||
Print.errln("Key from " + keyFile.getAbsolutePath() + " has unsupported asymmetric algorithm.");
|
||||
Print.trace(unsupportedAsymmetricAlgo);
|
||||
System.exit(unsupportedAsymmetricAlgo.getExitCode());
|
||||
} catch (SOPGPException.CertCannotSign certCannotSign) {
|
||||
Print.errln("Key from " + keyFile.getAbsolutePath() + " cannot sign.");
|
||||
Print.trace(certCannotSign);
|
||||
System.exit(1);
|
||||
} catch (SOPGPException.BadData badData) {
|
||||
Print.errln("Key file " + keyFile.getAbsolutePath() + " does not contain a valid OpenPGP private key.");
|
||||
Print.trace(badData);
|
||||
System.exit(badData.getExitCode());
|
||||
}
|
||||
}
|
||||
|
||||
for (File certFile : certs) {
|
||||
try (FileInputStream certIn = new FileInputStream(certFile)) {
|
||||
encrypt.withCert(certIn);
|
||||
} catch (FileNotFoundException e) {
|
||||
Print.errln("Certificate file " + certFile.getAbsolutePath() + " not found.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
} catch (IOException e) {
|
||||
Print.errln("IO Error.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
} catch (SOPGPException.UnsupportedAsymmetricAlgo unsupportedAsymmetricAlgo) {
|
||||
Print.errln("Certificate from " + certFile.getAbsolutePath() + " has unsupported asymmetric algorithm.");
|
||||
Print.trace(unsupportedAsymmetricAlgo);
|
||||
System.exit(unsupportedAsymmetricAlgo.getExitCode());
|
||||
} catch (SOPGPException.CertCannotEncrypt certCannotEncrypt) {
|
||||
Print.errln("Certificate from " + certFile.getAbsolutePath() + " is not capable of encryption.");
|
||||
Print.trace(certCannotEncrypt);
|
||||
System.exit(certCannotEncrypt.getExitCode());
|
||||
} catch (SOPGPException.BadData badData) {
|
||||
Print.errln("Certificate file " + certFile.getAbsolutePath() + " does not contain a valid OpenPGP certificate.");
|
||||
Print.trace(badData);
|
||||
System.exit(badData.getExitCode());
|
||||
}
|
||||
}
|
||||
|
||||
if (!armor) {
|
||||
encrypt.noArmor();
|
||||
}
|
||||
|
||||
try {
|
||||
Ready ready = encrypt.plaintext(System.in);
|
||||
ready.writeTo(System.out);
|
||||
} catch (IOException e) {
|
||||
Print.errln("IO Error.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,25 +13,21 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
|
||||
import static org.pgpainless.sop.Print.err_ln;
|
||||
import static org.pgpainless.sop.Print.print_ln;
|
||||
package sop.cli.picocli.commands;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.key.util.KeyRingUtils;
|
||||
import org.pgpainless.sop.Print;
|
||||
import picocli.CommandLine;
|
||||
import sop.Ready;
|
||||
import sop.cli.picocli.Print;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.ExtractCert;
|
||||
|
||||
@CommandLine.Command(name = "extract-cert",
|
||||
description = "Extract a public key certificate from a secret key from standard input",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class ExtractCert implements Runnable {
|
||||
public class ExtractCertCmd implements Runnable {
|
||||
|
||||
@CommandLine.Option(names = "--no-armor",
|
||||
description = "ASCII armor the output",
|
||||
|
@ -40,15 +36,22 @@ public class ExtractCert implements Runnable {
|
|||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(System.in);
|
||||
PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys);
|
||||
ExtractCert extractCert = SopCLI.getSop().extractCert();
|
||||
if (!armor) {
|
||||
extractCert.noArmor();
|
||||
}
|
||||
|
||||
print_ln(Print.toString(publicKeys, armor));
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("Error extracting certificate from keys;");
|
||||
err_ln(e.getMessage());
|
||||
try {
|
||||
Ready ready = extractCert.key(System.in);
|
||||
ready.writeTo(System.out);
|
||||
} catch (IOException e) {
|
||||
Print.errln("IO Error.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
} catch (SOPGPException.BadData badData) {
|
||||
Print.errln("Standard Input does not contain valid OpenPGP private key material.");
|
||||
Print.trace(badData);
|
||||
System.exit(badData.getExitCode());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 sop.cli.picocli.commands;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import sop.Ready;
|
||||
import sop.cli.picocli.Print;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.GenerateKey;
|
||||
|
||||
@CommandLine.Command(name = "generate-key",
|
||||
description = "Generate a secret key",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class GenerateKeyCmd implements Runnable {
|
||||
|
||||
@CommandLine.Option(names = "--no-armor",
|
||||
description = "ASCII armor the output",
|
||||
negatable = true)
|
||||
boolean armor = true;
|
||||
|
||||
@CommandLine.Parameters(description = "User-ID, eg. \"Alice <alice@example.com>\"")
|
||||
List<String> userId = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
GenerateKey generateKey = SopCLI.getSop().generateKey();
|
||||
for (String userId : userId) {
|
||||
generateKey.userId(userId);
|
||||
}
|
||||
|
||||
if (!armor) {
|
||||
generateKey.noArmor();
|
||||
}
|
||||
|
||||
try {
|
||||
Ready ready = generateKey.generate();
|
||||
ready.writeTo(System.out);
|
||||
} catch (SOPGPException.MissingArg missingArg) {
|
||||
Print.errln("Missing argument.");
|
||||
Print.trace(missingArg);
|
||||
System.exit(missingArg.getExitCode());
|
||||
} catch (SOPGPException.UnsupportedAsymmetricAlgo unsupportedAsymmetricAlgo) {
|
||||
Print.errln("Unsupported asymmetric algorithm.");
|
||||
Print.trace(unsupportedAsymmetricAlgo);
|
||||
System.exit(unsupportedAsymmetricAlgo.getExitCode());
|
||||
} catch (IOException e) {
|
||||
Print.errln("IO Error.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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 sop.cli.picocli.commands;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import sop.Ready;
|
||||
import sop.cli.picocli.Print;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
import sop.enums.SignAs;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Sign;
|
||||
|
||||
@CommandLine.Command(name = "sign",
|
||||
description = "Create a detached signature on the data from standard input",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class SignCmd implements Runnable {
|
||||
|
||||
@CommandLine.Option(names = "--no-armor",
|
||||
description = "ASCII armor the output",
|
||||
negatable = true)
|
||||
boolean armor = true;
|
||||
|
||||
@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}")
|
||||
SignAs type;
|
||||
|
||||
@CommandLine.Parameters(description = "Secret keys used for signing",
|
||||
paramLabel = "KEY")
|
||||
List<File> secretKeyFile = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Sign sign = SopCLI.getSop().sign();
|
||||
|
||||
if (type != null) {
|
||||
try {
|
||||
sign.mode(type);
|
||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
||||
Print.errln("Unsupported option '--as'");
|
||||
Print.trace(unsupportedOption);
|
||||
System.exit(unsupportedOption.getExitCode());
|
||||
}
|
||||
}
|
||||
|
||||
if (secretKeyFile.isEmpty()) {
|
||||
Print.errln("Missing required parameter 'KEY'.");
|
||||
System.exit(19);
|
||||
}
|
||||
|
||||
for (File keyFile : secretKeyFile) {
|
||||
try (FileInputStream keyIn = new FileInputStream(keyFile)) {
|
||||
sign.key(keyIn);
|
||||
} catch (FileNotFoundException e) {
|
||||
Print.errln("File " + keyFile.getAbsolutePath() + " does not exist.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
} catch (IOException e) {
|
||||
Print.errln("Cannot access file " + keyFile.getAbsolutePath());
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
} catch (SOPGPException.KeyIsProtected e) {
|
||||
Print.errln("Key " + keyFile.getName() + " is password protected.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
} catch (SOPGPException.BadData badData) {
|
||||
Print.errln("Bad data in key file " + keyFile.getAbsolutePath() + ":");
|
||||
Print.trace(badData);
|
||||
System.exit(badData.getExitCode());
|
||||
}
|
||||
}
|
||||
|
||||
if (!armor) {
|
||||
sign.noArmor();
|
||||
}
|
||||
|
||||
try {
|
||||
Ready ready = sign.data(System.in);
|
||||
ready.writeTo(System.out);
|
||||
} catch (IOException e) {
|
||||
Print.errln("IO Error.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
} catch (SOPGPException.ExpectedText expectedText) {
|
||||
Print.errln("Expected text input, but got binary data.");
|
||||
Print.trace(expectedText);
|
||||
System.exit(expectedText.getExitCode());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* 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 sop.cli.picocli.commands;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import sop.Verification;
|
||||
import sop.cli.picocli.DateParser;
|
||||
import sop.cli.picocli.Print;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Verify;
|
||||
|
||||
@CommandLine.Command(name = "verify",
|
||||
description = "Verify a detached signature over the data from standard input",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class VerifyCmd implements Runnable {
|
||||
|
||||
@CommandLine.Parameters(index = "0",
|
||||
description = "Detached signature",
|
||||
paramLabel = "SIGNATURE")
|
||||
File signature;
|
||||
|
||||
@CommandLine.Parameters(index = "1..*",
|
||||
arity = "1..*",
|
||||
description = "Public key certificates",
|
||||
paramLabel = "CERT")
|
||||
List<File> certificates = new ArrayList<>();
|
||||
|
||||
@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 (\"-\").",
|
||||
paramLabel = "DATE")
|
||||
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.",
|
||||
paramLabel = "DATE")
|
||||
String notAfter = "now";
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Verify verify = SopCLI.getSop().verify();
|
||||
if (notAfter != null) {
|
||||
try {
|
||||
verify.notAfter(DateParser.parseNotAfter(notAfter));
|
||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
||||
Print.errln("Unsupported option '--not-after'.");
|
||||
Print.trace(unsupportedOption);
|
||||
System.exit(unsupportedOption.getExitCode());
|
||||
}
|
||||
}
|
||||
if (notBefore != null) {
|
||||
try {
|
||||
verify.notBefore(DateParser.parseNotBefore(notBefore));
|
||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
||||
Print.errln("Unsupported option '--not-before'.");
|
||||
Print.trace(unsupportedOption);
|
||||
System.exit(unsupportedOption.getExitCode());
|
||||
}
|
||||
}
|
||||
|
||||
for (File certFile : certificates) {
|
||||
try (FileInputStream certIn = new FileInputStream(certFile)) {
|
||||
verify.cert(certIn);
|
||||
} catch (FileNotFoundException fileNotFoundException) {
|
||||
Print.errln("Certificate file " + certFile.getAbsolutePath() + " not found.");
|
||||
|
||||
Print.trace(fileNotFoundException);
|
||||
System.exit(1);
|
||||
} catch (IOException ioException) {
|
||||
Print.errln("IO Error.");
|
||||
Print.trace(ioException);
|
||||
System.exit(1);
|
||||
} catch (SOPGPException.BadData badData) {
|
||||
Print.errln("Certificate file " + certFile.getAbsolutePath() + " appears to not contain a valid OpenPGP certificate.");
|
||||
Print.trace(badData);
|
||||
System.exit(badData.getExitCode());
|
||||
}
|
||||
}
|
||||
|
||||
if (signature != null) {
|
||||
try (FileInputStream sigIn = new FileInputStream(signature)) {
|
||||
verify.signatures(sigIn);
|
||||
} catch (FileNotFoundException e) {
|
||||
Print.errln("Signature file " + signature.getAbsolutePath() + " does not exist.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
} catch (IOException e) {
|
||||
Print.errln("IO Error.");
|
||||
Print.trace(e);
|
||||
System.exit(1);
|
||||
} catch (SOPGPException.BadData badData) {
|
||||
Print.errln("File " + signature.getAbsolutePath() + " does not contain a valid OpenPGP signature.");
|
||||
Print.trace(badData);
|
||||
System.exit(badData.getExitCode());
|
||||
}
|
||||
}
|
||||
|
||||
List<Verification> verifications = null;
|
||||
try {
|
||||
verifications = verify.data(System.in);
|
||||
} catch (SOPGPException.NoSignature e) {
|
||||
Print.errln("No verifiable signature found.");
|
||||
Print.trace(e);
|
||||
System.exit(e.getExitCode());
|
||||
} catch (IOException ioException) {
|
||||
Print.errln("IO Error.");
|
||||
Print.trace(ioException);
|
||||
System.exit(1);
|
||||
} catch (SOPGPException.BadData badData) {
|
||||
Print.errln("Standard Input appears not to contain a valid OpenPGP message.");
|
||||
Print.trace(badData);
|
||||
System.exit(badData.getExitCode());
|
||||
}
|
||||
for (Verification verification : verifications) {
|
||||
Print.outln(verification.toString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 sop.cli.picocli.commands;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import sop.cli.picocli.Print;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
import sop.operation.Version;
|
||||
|
||||
@CommandLine.Command(name = "version", description = "Display version information about the tool",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class VersionCmd implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Version version = SopCLI.getSop().version();
|
||||
|
||||
Print.outln(version.getName() + " " + version.getVersion());
|
||||
}
|
||||
}
|
|
@ -16,4 +16,4 @@
|
|||
/**
|
||||
* Subcommands of the PGPainless SOP.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
package sop.cli.picocli.commands;
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
/**
|
||||
* Implementation of the Stateless OpenPGP Command Line Interface using Picocli.
|
||||
*/
|
||||
package sop.cli.picocli;
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.cli.picocli;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sop.util.UTCUtil;
|
||||
|
||||
public class DateParserTest {
|
||||
|
||||
@Test
|
||||
public void parseNotAfterDashReturnsEndOfTime() {
|
||||
assertEquals(DateParser.END_OF_TIME, DateParser.parseNotAfter("-"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseNotBeforeDashReturnsBeginningOfTime() {
|
||||
assertEquals(DateParser.BEGINNING_OF_TIME, DateParser.parseNotBefore("-"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseNotAfterNowReturnsNow() {
|
||||
assertEquals(new Date().getTime(), DateParser.parseNotAfter("now").getTime(), 1000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseNotBeforeNowReturnsNow() {
|
||||
assertEquals(new Date().getTime(), DateParser.parseNotBefore("now").getTime(), 1000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseNotAfterTimestamp() {
|
||||
String timestamp = "2019-10-24T23:48:29Z";
|
||||
Date date = DateParser.parseNotAfter(timestamp);
|
||||
assertEquals(timestamp, UTCUtil.formatUTCDate(date));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseNotBeforeTimestamp() {
|
||||
String timestamp = "2019-10-29T18:36:45Z";
|
||||
Date date = DateParser.parseNotBefore(timestamp);
|
||||
assertEquals(timestamp, UTCUtil.formatUTCDate(date));
|
||||
}
|
||||
}
|
42
sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java
Normal file
42
sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.cli.picocli;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sop.SOP;
|
||||
|
||||
public class SOPTest {
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(69)
|
||||
public void assertExitOnInvalidSubcommand() {
|
||||
SOP sop = mock(SOP.class);
|
||||
SopCLI.setSopInstance(sop);
|
||||
|
||||
SopCLI.main(new String[] {"invalid"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void assertThrowsIfNoSOPBackendSet() {
|
||||
SopCLI.SOP_INSTANCE = null;
|
||||
// At this point, no SOP backend is set, so an InvalidStateException triggers exit(1)
|
||||
SopCLI.main(new String[] {"armor"});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.cli.picocli.commands;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
||||
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sop.Ready;
|
||||
import sop.SOP;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
import sop.enums.ArmorLabel;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Armor;
|
||||
|
||||
public class ArmorCmdTest {
|
||||
|
||||
private Armor armor;
|
||||
private SOP sop;
|
||||
|
||||
@BeforeEach
|
||||
public void mockComponents() throws SOPGPException.BadData {
|
||||
armor = mock(Armor.class);
|
||||
sop = mock(SOP.class);
|
||||
when(sop.armor()).thenReturn(armor);
|
||||
when(armor.data(any())).thenReturn(nopReady());
|
||||
|
||||
SopCLI.setSopInstance(sop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertAllowNestedIsCalledWhenFlagged() throws SOPGPException.UnsupportedOption {
|
||||
SopCLI.main(new String[] {"armor", "--allow-nested"});
|
||||
verify(armor, times(1)).allowNested();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertAllowNestedIsNotCalledByDefault() throws SOPGPException.UnsupportedOption {
|
||||
SopCLI.main(new String[] {"armor"});
|
||||
verify(armor, never()).allowNested();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertLabelIsNotCalledByDefault() throws SOPGPException.UnsupportedOption {
|
||||
SopCLI.main(new String[] {"armor"});
|
||||
verify(armor, never()).label(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertLabelIsCalledWhenFlaggedWithArgument() throws SOPGPException.UnsupportedOption {
|
||||
for (ArmorLabel label : ArmorLabel.values()) {
|
||||
SopCLI.main(new String[] {"armor", "--label", label.name()});
|
||||
verify(armor, times(1)).label(label);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertDataIsAlwaysCalled() throws SOPGPException.BadData {
|
||||
SopCLI.main(new String[] {"armor"});
|
||||
verify(armor, times(1)).data(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(37)
|
||||
public void assertThrowsForInvalidLabel() {
|
||||
SopCLI.main(new String[] {"armor", "--label", "Invalid"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(37)
|
||||
public void ifLabelsUnsupportedExit37() throws SOPGPException.UnsupportedOption {
|
||||
when(armor.label(any())).thenThrow(new SOPGPException.UnsupportedOption());
|
||||
|
||||
SopCLI.main(new String[] {"armor", "--label", "Sig"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(37)
|
||||
public void ifAllowNestedUnsupportedExit37() throws SOPGPException.UnsupportedOption {
|
||||
when(armor.allowNested()).thenThrow(new SOPGPException.UnsupportedOption());
|
||||
|
||||
SopCLI.main(new String[] {"armor", "--allow-nested"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(41)
|
||||
public void ifBadDataExit41() throws SOPGPException.BadData {
|
||||
when(armor.data(any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
||||
|
||||
SopCLI.main(new String[] {"armor"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@FailOnSystemExit
|
||||
public void ifNoErrorsNoExit() {
|
||||
when(sop.armor()).thenReturn(armor);
|
||||
|
||||
SopCLI.main(new String[] {"armor"});
|
||||
}
|
||||
|
||||
private static Ready nopReady() {
|
||||
return new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) {
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.cli.picocli.commands;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sop.Ready;
|
||||
import sop.SOP;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Dearmor;
|
||||
|
||||
public class DearmorCmdTest {
|
||||
|
||||
private SOP sop;
|
||||
private Dearmor dearmor;
|
||||
|
||||
@BeforeEach
|
||||
public void mockComponents() throws IOException, SOPGPException.BadData {
|
||||
sop = mock(SOP.class);
|
||||
dearmor = mock(Dearmor.class);
|
||||
when(dearmor.data(any())).thenReturn(nopReady());
|
||||
when(sop.dearmor()).thenReturn(dearmor);
|
||||
|
||||
SopCLI.setSopInstance(sop);
|
||||
}
|
||||
|
||||
private static Ready nopReady() {
|
||||
return new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertDataIsCalled() throws IOException, SOPGPException.BadData {
|
||||
SopCLI.main(new String[] {"dearmor"});
|
||||
verify(dearmor, times(1)).data(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(41)
|
||||
public void assertBadDataCausesExit41() throws IOException, SOPGPException.BadData {
|
||||
when(dearmor.data(any())).thenThrow(new SOPGPException.BadData(new IOException("invalid armor")));
|
||||
SopCLI.main(new String[] {"dearmor"});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,346 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.cli.picocli.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import sop.DecryptionResult;
|
||||
import sop.ReadyWithResult;
|
||||
import sop.SOP;
|
||||
import sop.SessionKey;
|
||||
import sop.Verification;
|
||||
import sop.cli.picocli.DateParser;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Decrypt;
|
||||
import sop.util.HexUtil;
|
||||
import sop.util.UTCUtil;
|
||||
|
||||
public class DecryptCmdTest {
|
||||
|
||||
private Decrypt decrypt;
|
||||
|
||||
@BeforeEach
|
||||
public void mockComponents() throws SOPGPException.UnsupportedOption, SOPGPException.MissingArg, SOPGPException.BadData, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.PasswordNotHumanReadable, SOPGPException.CannotDecrypt {
|
||||
SOP sop = mock(SOP.class);
|
||||
decrypt = mock(Decrypt.class);
|
||||
|
||||
when(decrypt.verifyNotAfter(any())).thenReturn(decrypt);
|
||||
when(decrypt.verifyNotBefore(any())).thenReturn(decrypt);
|
||||
when(decrypt.withPassword(any())).thenReturn(decrypt);
|
||||
when(decrypt.withSessionKey(any())).thenReturn(decrypt);
|
||||
when(decrypt.withKey(any())).thenReturn(decrypt);
|
||||
when(decrypt.ciphertext(any())).thenReturn(nopReadyWithResult());
|
||||
|
||||
when(sop.decrypt()).thenReturn(decrypt);
|
||||
|
||||
SopCLI.setSopInstance(sop);
|
||||
}
|
||||
|
||||
private static ReadyWithResult<DecryptionResult> nopReadyWithResult() {
|
||||
return new ReadyWithResult<DecryptionResult>() {
|
||||
@Override
|
||||
public DecryptionResult writeTo(OutputStream outputStream) {
|
||||
return new DecryptionResult(null, Collections.emptyList());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(19)
|
||||
public void missingArgumentsExceptionCausesExit19() throws SOPGPException.MissingArg, SOPGPException.BadData, SOPGPException.CannotDecrypt {
|
||||
when(decrypt.ciphertext(any())).thenThrow(new SOPGPException.MissingArg("Missing arguments."));
|
||||
SopCLI.main(new String[] {"decrypt"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(41)
|
||||
public void badDataExceptionCausesExit41() throws SOPGPException.MissingArg, SOPGPException.BadData, SOPGPException.CannotDecrypt {
|
||||
when(decrypt.ciphertext(any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
||||
SopCLI.main(new String[] {"decrypt"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(31)
|
||||
public void assertNotHumanReadablePasswordCausesExit31() throws SOPGPException.PasswordNotHumanReadable,
|
||||
SOPGPException.UnsupportedOption {
|
||||
when(decrypt.withPassword(any())).thenThrow(new SOPGPException.PasswordNotHumanReadable());
|
||||
SopCLI.main(new String[] {"decrypt", "--with-password", "pretendThisIsNotReadable"});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertWithPasswordPassesPasswordDown() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption {
|
||||
SopCLI.main(new String[] {"decrypt", "--with-password", "orange"});
|
||||
verify(decrypt, times(1)).withPassword("orange");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(37)
|
||||
public void assertUnsupportedWithPasswordCausesExit37() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption {
|
||||
when(decrypt.withPassword(any())).thenThrow(new SOPGPException.UnsupportedOption());
|
||||
SopCLI.main(new String[] {"decrypt", "--with-password", "swordfish"});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertDefaultTimeRangesAreUsedIfNotOverwritten() throws SOPGPException.UnsupportedOption {
|
||||
Date now = new Date();
|
||||
SopCLI.main(new String[] {"decrypt"});
|
||||
verify(decrypt, times(1)).verifyNotBefore(DateParser.BEGINNING_OF_TIME);
|
||||
verify(decrypt, times(1)).verifyNotAfter(
|
||||
ArgumentMatchers.argThat(argument -> {
|
||||
// allow 1 second difference
|
||||
return Math.abs(now.getTime() - argument.getTime()) <= 1000;
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertVerifyNotAfterAndBeforeDashResultsInMaxTimeRange() throws SOPGPException.UnsupportedOption {
|
||||
SopCLI.main(new String[] {"decrypt", "--not-before", "-", "--not-after", "-"});
|
||||
verify(decrypt, times(1)).verifyNotBefore(DateParser.BEGINNING_OF_TIME);
|
||||
verify(decrypt, times(1)).verifyNotAfter(DateParser.END_OF_TIME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertVerifyNotAfterAndBeforeNowResultsInMinTimeRange() throws SOPGPException.UnsupportedOption {
|
||||
Date now = new Date();
|
||||
ArgumentMatcher<Date> isMaxOneSecOff = argument -> {
|
||||
// Allow less than 1 second difference
|
||||
return Math.abs(now.getTime() - argument.getTime()) <= 1000;
|
||||
};
|
||||
|
||||
SopCLI.main(new String[] {"decrypt", "--not-before", "now", "--not-after", "now"});
|
||||
verify(decrypt, times(1)).verifyNotAfter(ArgumentMatchers.argThat(isMaxOneSecOff));
|
||||
verify(decrypt, times(1)).verifyNotBefore(ArgumentMatchers.argThat(isMaxOneSecOff));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void assertMalformedDateInNotBeforeCausesExit1() {
|
||||
// ParserException causes exit(1)
|
||||
SopCLI.main(new String[] {"decrypt", "--not-before", "invalid"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void assertMalformedDateInNotAfterCausesExit1() {
|
||||
// ParserException causes exit(1)
|
||||
SopCLI.main(new String[] {"decrypt", "--not-after", "invalid"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(37)
|
||||
public void assertUnsupportedNotAfterCausesExit37() throws SOPGPException.UnsupportedOption {
|
||||
when(decrypt.verifyNotAfter(any())).thenThrow(new SOPGPException.UnsupportedOption());
|
||||
SopCLI.main(new String[] {"decrypt", "--not-after", "now"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(37)
|
||||
public void assertUnsupportedNotBeforeCausesExit37() throws SOPGPException.UnsupportedOption {
|
||||
when(decrypt.verifyNotBefore(any())).thenThrow(new SOPGPException.UnsupportedOption());
|
||||
SopCLI.main(new String[] {"decrypt", "--not-before", "now"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void assertExistingSessionKeyOutFileCausesExit1() throws IOException {
|
||||
File tempFile = File.createTempFile("existing-session-key-", ".tmp");
|
||||
tempFile.deleteOnExit();
|
||||
SopCLI.main(new String[] {"decrypt", "--session-key-out", tempFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(37)
|
||||
public void assertWhenSessionKeyCannotBeExtractedExit37() throws IOException {
|
||||
Path tempDir = Files.createTempDirectory("session-key-out-dir");
|
||||
File tempFile = new File(tempDir.toFile(), "session-key");
|
||||
tempFile.deleteOnExit();
|
||||
SopCLI.main(new String[] {"decrypt", "--session-key-out", tempFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertSessionKeyIsProperlyWrittenToSessionKeyFile() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException {
|
||||
byte[] key = "C7CBDAF42537776F12509B5168793C26B93294E5ABDFA73224FB0177123E9137".getBytes(StandardCharsets.UTF_8);
|
||||
when(decrypt.ciphertext(any())).thenReturn(new ReadyWithResult<DecryptionResult>() {
|
||||
@Override
|
||||
public DecryptionResult writeTo(OutputStream outputStream) {
|
||||
return new DecryptionResult(
|
||||
new SessionKey((byte) 9, key),
|
||||
Collections.emptyList()
|
||||
);
|
||||
}
|
||||
});
|
||||
Path tempDir = Files.createTempDirectory("session-key-out-dir");
|
||||
File tempFile = new File(tempDir.toFile(), "session-key");
|
||||
tempFile.deleteOnExit();
|
||||
SopCLI.main(new String[] {"decrypt", "--session-key-out", tempFile.getAbsolutePath()});
|
||||
|
||||
ByteArrayOutputStream bytesInFile = new ByteArrayOutputStream();
|
||||
try (FileInputStream fileIn = new FileInputStream(tempFile)) {
|
||||
byte[] buf = new byte[32];
|
||||
int read = fileIn.read(buf);
|
||||
while (read != -1) {
|
||||
bytesInFile.write(buf, 0, read);
|
||||
read = fileIn.read(buf);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] algAndKey = new byte[key.length + 1];
|
||||
algAndKey[0] = (byte) 9;
|
||||
System.arraycopy(key, 0, algAndKey, 1, key.length);
|
||||
assertArrayEquals(algAndKey, bytesInFile.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(29)
|
||||
public void assertUnableToDecryptExceptionResultsInExit29() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData {
|
||||
when(decrypt.ciphertext(any())).thenThrow(new SOPGPException.CannotDecrypt());
|
||||
SopCLI.main(new String[] {"decrypt"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(3)
|
||||
public void assertNoSignatureExceptionCausesExit3() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData {
|
||||
when(decrypt.ciphertext(any())).thenReturn(new ReadyWithResult<DecryptionResult>() {
|
||||
@Override
|
||||
public DecryptionResult writeTo(OutputStream outputStream) throws SOPGPException.NoSignature {
|
||||
throw new SOPGPException.NoSignature();
|
||||
}
|
||||
});
|
||||
SopCLI.main(new String[] {"decrypt"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(41)
|
||||
public void badDataInVerifyWithCausesExit41() throws IOException, SOPGPException.BadData {
|
||||
when(decrypt.verifyWithCert(any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
||||
File tempFile = File.createTempFile("verify-with-", ".tmp");
|
||||
SopCLI.main(new String[] {"decrypt", "--verify-with", tempFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void unexistentCertFileCausesExit1() {
|
||||
SopCLI.main(new String[] {"decrypt", "--verify-with", "invalid"});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void existingVerifyOutFileIsUnlinkedBeforeVerification() throws IOException, SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData {
|
||||
File certFile = File.createTempFile("existing-verify-out-cert", ".asc");
|
||||
File existingVerifyOut = File.createTempFile("existing-verify-out", ".tmp");
|
||||
byte[] data = "some data".getBytes(StandardCharsets.UTF_8);
|
||||
try (FileOutputStream out = new FileOutputStream(existingVerifyOut)) {
|
||||
out.write(data);
|
||||
}
|
||||
Date date = UTCUtil.parseUTCDate("2021-07-11T20:58:23Z");
|
||||
when(decrypt.ciphertext(any())).thenReturn(new ReadyWithResult<DecryptionResult>() {
|
||||
@Override
|
||||
public DecryptionResult writeTo(OutputStream outputStream) {
|
||||
return new DecryptionResult(null, Collections.singletonList(
|
||||
new Verification(
|
||||
date,
|
||||
"1B66A707819A920925BC6777C3E0AFC0B2DFF862",
|
||||
"C8CD564EBF8D7BBA90611D8D071773658BF6BF86"))
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
SopCLI.main(new String[] {"decrypt", "--verify-out", existingVerifyOut.getAbsolutePath(), "--verify-with", certFile.getAbsolutePath()});
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(existingVerifyOut))) {
|
||||
String line = reader.readLine();
|
||||
assertEquals("2021-07-11T20:58:23Z 1B66A707819A920925BC6777C3E0AFC0B2DFF862 C8CD564EBF8D7BBA90611D8D071773658BF6BF86", line);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertWithSessionKeyIsPassedDown() throws SOPGPException.UnsupportedOption {
|
||||
SessionKey key1 = new SessionKey((byte) 9, HexUtil.hexToBytes("C7CBDAF42537776F12509B5168793C26B93294E5ABDFA73224FB0177123E9137"));
|
||||
SessionKey key2 = new SessionKey((byte) 9, HexUtil.hexToBytes("FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD"));
|
||||
SopCLI.main(new String[] {"decrypt",
|
||||
"--with-session-key", "9:C7CBDAF42537776F12509B5168793C26B93294E5ABDFA73224FB0177123E9137",
|
||||
"--with-session-key", "9:FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD"});
|
||||
verify(decrypt).withSessionKey(key1);
|
||||
verify(decrypt).withSessionKey(key2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void assertMalformedSessionKeysResultInExit1() {
|
||||
SopCLI.main(new String[] {"decrypt",
|
||||
"--with-session-key", "C7CBDAF42537776F12509B5168793C26B93294E5ABDFA73224FB0177123E9137"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(41)
|
||||
public void assertBadDataInKeysResultsInExit41() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, IOException {
|
||||
when(decrypt.withKey(any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
||||
File tempKeyFile = File.createTempFile("key-", ".tmp");
|
||||
SopCLI.main(new String[] {"decrypt", tempKeyFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void assertKeyFileNotFoundCausesExit1() {
|
||||
SopCLI.main(new String[] {"decrypt", "nonexistent-key"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void assertProtectedKeyCausesExit1() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData {
|
||||
when(decrypt.withKey(any())).thenThrow(new SOPGPException.KeyIsProtected());
|
||||
File tempKeyFile = File.createTempFile("key-", ".tmp");
|
||||
SopCLI.main(new String[] {"decrypt", tempKeyFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(13)
|
||||
public void assertUnsupportedAlgorithmExceptionCausesExit13() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, IOException {
|
||||
when(decrypt.withKey(any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo(new IOException()));
|
||||
File tempKeyFile = File.createTempFile("key-", ".tmp");
|
||||
SopCLI.main(new String[] {"decrypt", tempKeyFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(23)
|
||||
public void verifyOutWithoutVerifyWithCausesExit23() {
|
||||
SopCLI.main(new String[] {"decrypt", "--verify-out", "out.file"});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.cli.picocli.commands;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sop.Ready;
|
||||
import sop.SOP;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
import sop.enums.EncryptAs;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Encrypt;
|
||||
|
||||
public class EncryptCmdTest {
|
||||
|
||||
Encrypt encrypt;
|
||||
|
||||
@BeforeEach
|
||||
public void mockComponents() throws IOException {
|
||||
encrypt = mock(Encrypt.class);
|
||||
when(encrypt.plaintext(any())).thenReturn(new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
SOP sop = mock(SOP.class);
|
||||
when(sop.encrypt()).thenReturn(encrypt);
|
||||
|
||||
SopCLI.setSopInstance(sop);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(19)
|
||||
public void missingPasswordAndCertFileCauseExit19() {
|
||||
SopCLI.main(new String[] {"encrypt", "--no-armor"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(37)
|
||||
public void as_unsupportedEncryptAsCausesExit37() throws SOPGPException.UnsupportedOption {
|
||||
when(encrypt.mode(any())).thenThrow(new SOPGPException.UnsupportedOption());
|
||||
|
||||
SopCLI.main(new String[] {"encrypt", "--as", "Binary"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(37)
|
||||
public void as_invalidModeOptionCausesExit37() {
|
||||
SopCLI.main(new String[] {"encrypt", "--as", "invalid"});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void as_modeIsPassedDown() throws SOPGPException.UnsupportedOption {
|
||||
for (EncryptAs mode : EncryptAs.values()) {
|
||||
SopCLI.main(new String[] {"encrypt", "--as", mode.name(), "--with-password", "0rbit"});
|
||||
verify(encrypt, times(1)).mode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(31)
|
||||
public void withPassword_notHumanReadablePasswordCausesExit31() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption {
|
||||
when(encrypt.withPassword("pretendThisIsNotReadable")).thenThrow(new SOPGPException.PasswordNotHumanReadable());
|
||||
|
||||
SopCLI.main(new String[] {"encrypt", "--with-password", "pretendThisIsNotReadable"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(37)
|
||||
public void withPassword_unsupportedWithPasswordCausesExit37() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption {
|
||||
when(encrypt.withPassword(any())).thenThrow(new SOPGPException.UnsupportedOption());
|
||||
|
||||
SopCLI.main(new String[] {"encrypt", "--with-password", "orange"});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void signWith_multipleTimesGetPassedDown() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotSign, SOPGPException.BadData {
|
||||
File keyFile1 = File.createTempFile("sign-with-1-", ".asc");
|
||||
File keyFile2 = File.createTempFile("sign-with-2-", ".asc");
|
||||
|
||||
SopCLI.main(new String[] {"encrypt", "--with-password", "password", "--sign-with", keyFile1.getAbsolutePath(), "--sign-with", keyFile2.getAbsolutePath()});
|
||||
verify(encrypt, times(2)).signWith(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void signWith_nonExistentKeyFileCausesExit1() {
|
||||
SopCLI.main(new String[] {"encrypt", "--with-password", "admin", "--sign-with", "nonExistent.asc"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void signWith_keyIsProtectedCausesExit1() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotSign, SOPGPException.BadData, IOException {
|
||||
when(encrypt.signWith(any())).thenThrow(new SOPGPException.KeyIsProtected());
|
||||
File keyFile = File.createTempFile("sign-with", ".asc");
|
||||
SopCLI.main(new String[] {"encrypt", "--sign-with", keyFile.getAbsolutePath(), "--with-password", "starship"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(13)
|
||||
public void signWith_unsupportedAsymmetricAlgoCausesExit13() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotSign, SOPGPException.BadData, IOException {
|
||||
when(encrypt.signWith(any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo(new Exception()));
|
||||
File keyFile = File.createTempFile("sign-with", ".asc");
|
||||
SopCLI.main(new String[] {"encrypt", "--with-password", "123456", "--sign-with", keyFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void signWith_certCannotSignCausesExit1() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotSign, SOPGPException.BadData {
|
||||
when(encrypt.signWith(any())).thenThrow(new SOPGPException.CertCannotSign());
|
||||
File keyFile = File.createTempFile("sign-with", ".asc");
|
||||
SopCLI.main(new String[] {"encrypt", "--with-password", "dragon", "--sign-with", keyFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(41)
|
||||
public void signWith_badDataCausesExit41() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotSign, SOPGPException.BadData, IOException {
|
||||
when(encrypt.signWith(any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
||||
File keyFile = File.createTempFile("sign-with", ".asc");
|
||||
SopCLI.main(new String[] {"encrypt", "--with-password", "orange", "--sign-with", keyFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void cert_nonExistentCertFileCausesExit1() {
|
||||
SopCLI.main(new String[] {"encrypt", "invalid.asc"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(13)
|
||||
public void cert_unsupportedAsymmetricAlgorithmCausesExit13() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData {
|
||||
when(encrypt.withCert(any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo(new Exception()));
|
||||
File certFile = File.createTempFile("cert", ".asc");
|
||||
SopCLI.main(new String[] {"encrypt", certFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(17)
|
||||
public void cert_certCannotEncryptCausesExit17() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData {
|
||||
when(encrypt.withCert(any())).thenThrow(new SOPGPException.CertCannotEncrypt());
|
||||
File certFile = File.createTempFile("cert", ".asc");
|
||||
SopCLI.main(new String[] {"encrypt", certFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(41)
|
||||
public void cert_badDataCausesExit41() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData {
|
||||
when(encrypt.withCert(any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
||||
File certFile = File.createTempFile("cert", ".asc");
|
||||
SopCLI.main(new String[] {"encrypt", certFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noArmor_notCalledByDefault() {
|
||||
SopCLI.main(new String[] {"encrypt", "--with-password", "clownfish"});
|
||||
verify(encrypt, never()).noArmor();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noArmor_callGetsPassedDown() {
|
||||
SopCLI.main(new String[] {"encrypt", "--with-password", "monkey", "--no-armor"});
|
||||
verify(encrypt, times(1)).noArmor();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void writeTo_ioExceptionCausesExit1() throws IOException {
|
||||
when(encrypt.plaintext(any())).thenReturn(new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
});
|
||||
|
||||
SopCLI.main(new String[] {"encrypt", "--with-password", "wildcat"});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 sop.cli.picocli.commands;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sop.Ready;
|
||||
import sop.SOP;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.ExtractCert;
|
||||
|
||||
public class ExtractCertCmdTest {
|
||||
|
||||
ExtractCert extractCert;
|
||||
|
||||
@BeforeEach
|
||||
public void mockComponents() throws IOException, SOPGPException.BadData {
|
||||
extractCert = mock(ExtractCert.class);
|
||||
when(extractCert.key(any())).thenReturn(new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) {
|
||||
}
|
||||
});
|
||||
|
||||
SOP sop = mock(SOP.class);
|
||||
when(sop.extractCert()).thenReturn(extractCert);
|
||||
|
||||
SopCLI.setSopInstance(sop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noArmor_notCalledByDefault() {
|
||||
SopCLI.main(new String[] {"extract-cert"});
|
||||
verify(extractCert, never()).noArmor();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noArmor_passedDown() {
|
||||
SopCLI.main(new String[] {"extract-cert", "--no-armor"});
|
||||
verify(extractCert, times(1)).noArmor();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void key_ioExceptionCausesExit1() throws IOException, SOPGPException.BadData {
|
||||
when(extractCert.key(any())).thenReturn(new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
});
|
||||
SopCLI.main(new String[] {"extract-cert"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(41)
|
||||
public void key_badDataCausesExit41() throws IOException, SOPGPException.BadData {
|
||||
when(extractCert.key(any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
||||
SopCLI.main(new String[] {"extract-cert"});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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 sop.cli.picocli.commands;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mockito;
|
||||
import sop.Ready;
|
||||
import sop.SOP;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.GenerateKey;
|
||||
|
||||
public class GenerateKeyCmdTest {
|
||||
|
||||
GenerateKey generateKey;
|
||||
|
||||
@BeforeEach
|
||||
public void mockComponents() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException {
|
||||
generateKey = mock(GenerateKey.class);
|
||||
when(generateKey.generate()).thenReturn(new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
SOP sop = mock(SOP.class);
|
||||
when(sop.generateKey()).thenReturn(generateKey);
|
||||
|
||||
SopCLI.setSopInstance(sop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noArmor_notCalledByDefault() {
|
||||
SopCLI.main(new String[] {"generate-key", "Alice"});
|
||||
verify(generateKey, never()).noArmor();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noArmor_passedDown() {
|
||||
SopCLI.main(new String[] {"generate-key", "--no-armor", "Alice"});
|
||||
verify(generateKey, times(1)).noArmor();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userId_multipleUserIdsPassedDownInProperOrder() {
|
||||
SopCLI.main(new String[] {"generate-key", "Alice <alice@pgpainless.org>", "Bob <bob@pgpainless.org>"});
|
||||
|
||||
InOrder inOrder = Mockito.inOrder(generateKey);
|
||||
inOrder.verify(generateKey).userId("Alice <alice@pgpainless.org>");
|
||||
inOrder.verify(generateKey).userId("Bob <bob@pgpainless.org>");
|
||||
|
||||
verify(generateKey, times(2)).userId(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(19)
|
||||
public void missingArgumentCausesExit19() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException {
|
||||
// TODO: RFC4880-bis and the current Stateless OpenPGP CLI spec allow keys to have no user-ids,
|
||||
// so we might want to change this test in the future.
|
||||
when(generateKey.generate()).thenThrow(new SOPGPException.MissingArg("Missing user-id."));
|
||||
SopCLI.main(new String[] {"generate-key"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(13)
|
||||
public void unsupportedAsymmetricAlgorithmCausesExit13() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException {
|
||||
when(generateKey.generate()).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo(new Exception()));
|
||||
SopCLI.main(new String[] {"generate-key", "Alice"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void ioExceptionCausesExit1() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException {
|
||||
when(generateKey.generate()).thenReturn(new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
});
|
||||
SopCLI.main(new String[] {"generate-key", "Alice"});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.cli.picocli.commands;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sop.Ready;
|
||||
import sop.SOP;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Sign;
|
||||
|
||||
public class SignCmdTest {
|
||||
|
||||
Sign sign;
|
||||
File keyFile;
|
||||
|
||||
@BeforeEach
|
||||
public void mockComponents() throws IOException, SOPGPException.ExpectedText {
|
||||
sign = mock(Sign.class);
|
||||
when(sign.data(any())).thenReturn(new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
SOP sop = mock(SOP.class);
|
||||
when(sop.sign()).thenReturn(sign);
|
||||
|
||||
SopCLI.setSopInstance(sop);
|
||||
|
||||
keyFile = File.createTempFile("sign-", ".asc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void as_optionsAreCaseInsensitive() {
|
||||
SopCLI.main(new String[] {"sign", "--as", "Binary", keyFile.getAbsolutePath()});
|
||||
SopCLI.main(new String[] {"sign", "--as", "binary", keyFile.getAbsolutePath()});
|
||||
SopCLI.main(new String[] {"sign", "--as", "BINARY", keyFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(37)
|
||||
public void as_invalidOptionCausesExit37() {
|
||||
SopCLI.main(new String[] {"sign", "--as", "Invalid", keyFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(37)
|
||||
public void as_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption {
|
||||
when(sign.mode(any())).thenThrow(new SOPGPException.UnsupportedOption());
|
||||
SopCLI.main(new String[] {"sign", "--as", "binary", keyFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void key_nonExistentKeyFileCausesExit1() {
|
||||
SopCLI.main(new String[] {"sign", "invalid.asc"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void key_keyIsProtectedCausesExit1() throws SOPGPException.KeyIsProtected, IOException, SOPGPException.BadData {
|
||||
when(sign.key(any())).thenThrow(new SOPGPException.KeyIsProtected());
|
||||
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(41)
|
||||
public void key_badDataCausesExit41() throws SOPGPException.KeyIsProtected, IOException, SOPGPException.BadData {
|
||||
when(sign.key(any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
||||
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(19)
|
||||
public void key_missingKeyFileCausesExit19() {
|
||||
SopCLI.main(new String[] {"sign"});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noArmor_notCalledByDefault() {
|
||||
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()});
|
||||
verify(sign, never()).noArmor();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noArmor_passedDown() {
|
||||
SopCLI.main(new String[] {"sign", "--no-armor", keyFile.getAbsolutePath()});
|
||||
verify(sign, times(1)).noArmor();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void data_ioExceptionCausesExit1() throws IOException, SOPGPException.ExpectedText {
|
||||
when(sign.data(any())).thenReturn(new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
});
|
||||
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(53)
|
||||
public void data_expectedTextExceptionCausesExit53() throws IOException, SOPGPException.ExpectedText {
|
||||
when(sign.data(any())).thenThrow(new SOPGPException.ExpectedText());
|
||||
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* 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 sop.cli.picocli.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import sop.SOP;
|
||||
import sop.Verification;
|
||||
import sop.cli.picocli.DateParser;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Verify;
|
||||
import sop.util.UTCUtil;
|
||||
|
||||
public class VerifyCmdTest {
|
||||
|
||||
Verify verify;
|
||||
File signature;
|
||||
File cert;
|
||||
|
||||
PrintStream originalSout;
|
||||
|
||||
@BeforeEach
|
||||
public void prepare() throws SOPGPException.UnsupportedOption, SOPGPException.BadData, SOPGPException.NoSignature, IOException {
|
||||
originalSout = System.out;
|
||||
|
||||
verify = mock(Verify.class);
|
||||
when(verify.notBefore(any())).thenReturn(verify);
|
||||
when(verify.notAfter(any())).thenReturn(verify);
|
||||
when(verify.cert(any())).thenReturn(verify);
|
||||
when(verify.signatures(any())).thenReturn(verify);
|
||||
when(verify.data(any())).thenReturn(
|
||||
Collections.singletonList(
|
||||
new Verification(
|
||||
UTCUtil.parseUTCDate("2019-10-29T18:36:45Z"),
|
||||
"EB85BB5FA33A75E15E944E63F231550C4F47E38E",
|
||||
"EB85BB5FA33A75E15E944E63F231550C4F47E38E")
|
||||
)
|
||||
);
|
||||
|
||||
SOP sop = mock(SOP.class);
|
||||
when(sop.verify()).thenReturn(verify);
|
||||
|
||||
SopCLI.setSopInstance(sop);
|
||||
|
||||
signature = File.createTempFile("signature-", ".asc");
|
||||
cert = File.createTempFile("cert-", ".asc");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void restoreSout() {
|
||||
System.setOut(originalSout);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notAfter_passedDown() throws SOPGPException.UnsupportedOption {
|
||||
Date date = UTCUtil.parseUTCDate("2019-10-29T18:36:45Z");
|
||||
SopCLI.main(new String[] {"verify", "--not-after", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
||||
verify(verify, times(1)).notAfter(date);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notAfter_now() throws SOPGPException.UnsupportedOption {
|
||||
Date now = new Date();
|
||||
SopCLI.main(new String[] {"verify", "--not-after", "now", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
||||
verify(verify, times(1)).notAfter(dateMatcher(now));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notAfter_dashCountsAsEndOfTime() throws SOPGPException.UnsupportedOption {
|
||||
SopCLI.main(new String[] {"verify", "--not-after", "-", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
||||
verify(verify, times(1)).notAfter(DateParser.END_OF_TIME);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(37)
|
||||
public void notAfter_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption {
|
||||
when(verify.notAfter(any())).thenThrow(new SOPGPException.UnsupportedOption());
|
||||
SopCLI.main(new String[] {"verify", "--not-after", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notBefore_passedDown() throws SOPGPException.UnsupportedOption {
|
||||
Date date = UTCUtil.parseUTCDate("2019-10-29T18:36:45Z");
|
||||
SopCLI.main(new String[] {"verify", "--not-before", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
||||
verify(verify, times(1)).notBefore(date);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notBefore_now() throws SOPGPException.UnsupportedOption {
|
||||
Date now = new Date();
|
||||
SopCLI.main(new String[] {"verify", "--not-before", "now", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
||||
verify(verify, times(1)).notBefore(dateMatcher(now));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notBefore_dashCountsAsBeginningOfTime() throws SOPGPException.UnsupportedOption {
|
||||
SopCLI.main(new String[] {"verify", "--not-before", "-", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
||||
verify(verify, times(1)).notBefore(DateParser.BEGINNING_OF_TIME);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(37)
|
||||
public void notBefore_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption {
|
||||
when(verify.notBefore(any())).thenThrow(new SOPGPException.UnsupportedOption());
|
||||
SopCLI.main(new String[] {"verify", "--not-before", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notBeforeAndNotAfterAreCalledWithDefaultValues() throws SOPGPException.UnsupportedOption {
|
||||
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
||||
verify(verify, times(1)).notAfter(dateMatcher(new Date()));
|
||||
verify(verify, times(1)).notBefore(DateParser.BEGINNING_OF_TIME);
|
||||
}
|
||||
|
||||
private static Date dateMatcher(Date date) {
|
||||
return ArgumentMatchers.argThat(argument -> Math.abs(argument.getTime() - date.getTime()) < 1000);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void cert_fileNotFoundCausesExit1() {
|
||||
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), "invalid.asc"});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(41)
|
||||
public void cert_badDataCausesExit41() throws SOPGPException.BadData {
|
||||
when(verify.cert(any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
||||
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(1)
|
||||
public void signature_fileNotFoundCausesExit1() {
|
||||
SopCLI.main(new String[] {"verify", "invalid.sig", cert.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(41)
|
||||
public void signature_badDataCausesExit41() throws SOPGPException.BadData {
|
||||
when(verify.signatures(any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
||||
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(3)
|
||||
public void data_noSignaturesCausesExit3() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData {
|
||||
when(verify.data(any())).thenThrow(new SOPGPException.NoSignature());
|
||||
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(41)
|
||||
public void data_badDataCausesExit41() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData {
|
||||
when(verify.data(any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
||||
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resultIsPrintedProperly() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData {
|
||||
when(verify.data(any())).thenReturn(Arrays.asList(
|
||||
new Verification(UTCUtil.parseUTCDate("2019-10-29T18:36:45Z"),
|
||||
"EB85BB5FA33A75E15E944E63F231550C4F47E38E",
|
||||
"EB85BB5FA33A75E15E944E63F231550C4F47E38E"),
|
||||
new Verification(UTCUtil.parseUTCDate("2019-10-24T23:48:29Z"),
|
||||
"C90E6D36200A1B922A1509E77618196529AE5FF8",
|
||||
"C4BC2DDB38CCE96485EBE9C2F20691179038E5C6")
|
||||
));
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
|
||||
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
||||
|
||||
System.setOut(originalSout);
|
||||
|
||||
String expected = "2019-10-29T18:36:45Z EB85BB5FA33A75E15E944E63F231550C4F47E38E EB85BB5FA33A75E15E944E63F231550C4F47E38E\n" +
|
||||
"2019-10-24T23:48:29Z C90E6D36200A1B922A1509E77618196529AE5FF8 C4BC2DDB38CCE96485EBE9C2F20691179038E5C6\n";
|
||||
|
||||
assertEquals(expected, out.toString());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.cli.picocli.commands;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sop.SOP;
|
||||
import sop.cli.picocli.SopCLI;
|
||||
import sop.operation.Version;
|
||||
|
||||
public class VersionCmdTest {
|
||||
|
||||
private SOP sop;
|
||||
private Version version;
|
||||
|
||||
@BeforeEach
|
||||
public void mockComponents() {
|
||||
sop = mock(SOP.class);
|
||||
version = mock(Version.class);
|
||||
when(version.getName()).thenReturn("MockSop");
|
||||
when(version.getVersion()).thenReturn("1.0");
|
||||
when(sop.version()).thenReturn(version);
|
||||
|
||||
SopCLI.setSopInstance(sop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertVersionCommandWorks() {
|
||||
SopCLI.main(new String[] {"version"});
|
||||
verify(version, times(1)).getVersion();
|
||||
verify(version, times(1)).getName();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(37)
|
||||
public void assertInvalidOptionResultsInExit37() {
|
||||
SopCLI.main(new String[] {"version", "--invalid"});
|
||||
}
|
||||
}
|
18
sop-java/build.gradle
Normal file
18
sop-java/build.gradle
Normal file
|
@ -0,0 +1,18 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
group 'org.pgpainless'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
35
sop-java/src/main/java/sop/ByteArrayAndResult.java
Normal file
35
sop-java/src/main/java/sop/ByteArrayAndResult.java
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop;
|
||||
|
||||
public class ByteArrayAndResult<T> {
|
||||
|
||||
private final byte[] bytes;
|
||||
private final T result;
|
||||
|
||||
public ByteArrayAndResult(byte[] bytes, T result) {
|
||||
this.bytes = bytes;
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public T getResult() {
|
||||
return result;
|
||||
}
|
||||
}
|
40
sop-java/src/main/java/sop/DecryptionResult.java
Normal file
40
sop-java/src/main/java/sop/DecryptionResult.java
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import sop.util.Optional;
|
||||
|
||||
public class DecryptionResult {
|
||||
|
||||
private final Optional<SessionKey> sessionKey;
|
||||
private final List<Verification> verifications;
|
||||
|
||||
public DecryptionResult(SessionKey sessionKey, List<Verification> verifications) {
|
||||
this.sessionKey = Optional.ofNullable(sessionKey);
|
||||
this.verifications = Collections.unmodifiableList(verifications);
|
||||
}
|
||||
|
||||
public Optional<SessionKey> getSessionKey() {
|
||||
return sessionKey;
|
||||
}
|
||||
|
||||
public List<Verification> getVerifications() {
|
||||
return new ArrayList<>(verifications);
|
||||
}
|
||||
}
|
31
sop-java/src/main/java/sop/Ready.java
Normal file
31
sop-java/src/main/java/sop/Ready.java
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public abstract class Ready {
|
||||
|
||||
public abstract void writeTo(OutputStream outputStream) throws IOException;
|
||||
|
||||
public byte[] getBytes() throws IOException {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
writeTo(bytes);
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
}
|
33
sop-java/src/main/java/sop/ReadyWithResult.java
Normal file
33
sop-java/src/main/java/sop/ReadyWithResult.java
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public abstract class ReadyWithResult<T> {
|
||||
|
||||
public abstract T writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature;
|
||||
|
||||
public ByteArrayAndResult<T> toBytes() throws IOException, SOPGPException.NoSignature {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
T result = writeTo(bytes);
|
||||
return new ByteArrayAndResult<>(bytes.toByteArray(), result);
|
||||
}
|
||||
}
|
103
sop-java/src/main/java/sop/SOP.java
Normal file
103
sop-java/src/main/java/sop/SOP.java
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop;
|
||||
|
||||
import sop.operation.Armor;
|
||||
import sop.operation.Dearmor;
|
||||
import sop.operation.Decrypt;
|
||||
import sop.operation.Encrypt;
|
||||
import sop.operation.ExtractCert;
|
||||
import sop.operation.GenerateKey;
|
||||
import sop.operation.Sign;
|
||||
import sop.operation.Verify;
|
||||
import sop.operation.Version;
|
||||
|
||||
/**
|
||||
* Stateless OpenPGP Interface.
|
||||
*/
|
||||
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 Sign}.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
Sign sign();
|
||||
|
||||
/**
|
||||
* Verify detached signatures.
|
||||
* Customize the operation using the builder {@link Verify}.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
Verify verify();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
75
sop-java/src/main/java/sop/SessionKey.java
Normal file
75
sop-java/src/main/java/sop/SessionKey.java
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import sop.util.HexUtil;
|
||||
|
||||
public class SessionKey {
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "" + (int) getAlgorithm() + ':' + HexUtil.bytesToHex(sessionKey);
|
||||
}
|
||||
}
|
69
sop-java/src/main/java/sop/Verification.java
Normal file
69
sop-java/src/main/java/sop/Verification.java
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import sop.util.UTCUtil;
|
||||
|
||||
public class Verification {
|
||||
|
||||
private final Date creationTime;
|
||||
private final String signingKeyFingerprint;
|
||||
private final String signingCertFingerprint;
|
||||
|
||||
public Verification(Date creationTime, String signingKeyFingerprint, String signingCertFingerprint) {
|
||||
this.creationTime = creationTime;
|
||||
this.signingKeyFingerprint = signingKeyFingerprint;
|
||||
this.signingCertFingerprint = signingCertFingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the signatures creation time.
|
||||
*
|
||||
* @return signature creation time
|
||||
*/
|
||||
public Date getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fingerprint of the signing (sub)key.
|
||||
*
|
||||
* @return signing key fingerprint
|
||||
*/
|
||||
public String getSigningKeyFingerprint() {
|
||||
return signingKeyFingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fingerprint fo the signing certificate.
|
||||
*
|
||||
* @return signing certificate fingerprint
|
||||
*/
|
||||
public String getSigningCertFingerprint() {
|
||||
return signingCertFingerprint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return UTCUtil.formatUTCDate(getCreationTime()) +
|
||||
' ' +
|
||||
getSigningKeyFingerprint() +
|
||||
' ' +
|
||||
getSigningCertFingerprint();
|
||||
}
|
||||
}
|
24
sop-java/src/main/java/sop/enums/ArmorLabel.java
Normal file
24
sop-java/src/main/java/sop/enums/ArmorLabel.java
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.enums;
|
||||
|
||||
public enum ArmorLabel {
|
||||
Auto,
|
||||
Sig,
|
||||
Key,
|
||||
Cert,
|
||||
Message
|
||||
}
|
22
sop-java/src/main/java/sop/enums/EncryptAs.java
Normal file
22
sop-java/src/main/java/sop/enums/EncryptAs.java
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.enums;
|
||||
|
||||
public enum EncryptAs {
|
||||
Binary,
|
||||
Text,
|
||||
MIME
|
||||
}
|
21
sop-java/src/main/java/sop/enums/SignAs.java
Normal file
21
sop-java/src/main/java/sop/enums/SignAs.java
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.enums;
|
||||
|
||||
public enum SignAs {
|
||||
Binary,
|
||||
Text
|
||||
}
|
21
sop-java/src/main/java/sop/enums/package-info.java
Normal file
21
sop-java/src/main/java/sop/enums/package-info.java
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stateless OpenPGP Interface for Java.
|
||||
* Enumerations.
|
||||
*/
|
||||
package sop.enums;
|
157
sop-java/src/main/java/sop/exception/SOPGPException.java
Normal file
157
sop-java/src/main/java/sop/exception/SOPGPException.java
Normal file
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.exception;
|
||||
|
||||
public class SOPGPException extends Exception {
|
||||
|
||||
public SOPGPException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SOPGPException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SOPGPException(Throwable e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
public static class NoSignature extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 3;
|
||||
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
public static class UnsupportedAsymmetricAlgo extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 13;
|
||||
|
||||
public UnsupportedAsymmetricAlgo(Throwable e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CertCannotEncrypt extends SOPGPException {
|
||||
public static final int EXIT_CODE = 17;
|
||||
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CertCannotSign extends SOPGPException {
|
||||
|
||||
}
|
||||
|
||||
public static class MissingArg extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 19;
|
||||
|
||||
public MissingArg(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
public static class IncompleteVerification extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 23;
|
||||
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CannotDecrypt extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 29;
|
||||
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PasswordNotHumanReadable extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 31;
|
||||
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
public static class UnsupportedOption extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 37;
|
||||
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
public static class BadData extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 41;
|
||||
|
||||
public BadData(Throwable e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ExpectedText extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 53;
|
||||
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
public static class OutputExists extends SOPGPException {
|
||||
|
||||
}
|
||||
|
||||
public static class KeyIsProtected extends SOPGPException {
|
||||
|
||||
}
|
||||
|
||||
public static class AmbiguousInput extends SOPGPException {
|
||||
|
||||
}
|
||||
|
||||
public static class NotImplemented extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 69;
|
||||
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
}
|
21
sop-java/src/main/java/sop/exception/package-info.java
Normal file
21
sop-java/src/main/java/sop/exception/package-info.java
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stateless OpenPGP Interface for Java.
|
||||
* Exception classes.
|
||||
*/
|
||||
package sop.exception;
|
48
sop-java/src/main/java/sop/operation/Armor.java
Normal file
48
sop-java/src/main/java/sop/operation/Armor.java
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.operation;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Allow nested Armoring.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
Armor allowNested() throws SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Armor the provided data.
|
||||
*
|
||||
* @param data input stream of unarmored OpenPGP data
|
||||
* @return armored data
|
||||
*/
|
||||
Ready data(InputStream data) throws SOPGPException.BadData;
|
||||
}
|
33
sop-java/src/main/java/sop/operation/Dearmor.java
Normal file
33
sop-java/src/main/java/sop/operation/Dearmor.java
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.operation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import sop.Ready;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public interface Dearmor {
|
||||
|
||||
/**
|
||||
* Dearmor armored OpenPGP data.
|
||||
*
|
||||
* @param data armored OpenPGP data
|
||||
* @return input stream of unarmored data
|
||||
*/
|
||||
Ready data(InputStream data) throws SOPGPException.BadData, IOException;
|
||||
}
|
94
sop-java/src/main/java/sop/operation/Decrypt.java
Normal file
94
sop-java/src/main/java/sop/operation/Decrypt.java
Normal file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.operation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
|
||||
import sop.DecryptionResult;
|
||||
import sop.ReadyWithResult;
|
||||
import sop.SessionKey;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public interface Decrypt {
|
||||
|
||||
/**
|
||||
* Makes the SOP consider signatures before this date invalid.
|
||||
*
|
||||
* @param timestamp timestamp
|
||||
* @return builder instance
|
||||
*/
|
||||
Decrypt verifyNotBefore(Date timestamp)
|
||||
throws SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Makes the SOP consider signatures after this date invalid.
|
||||
*
|
||||
* @param timestamp timestamp
|
||||
* @return builder instance
|
||||
*/
|
||||
Decrypt verifyNotAfter(Date timestamp)
|
||||
throws SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Adds the verification cert.
|
||||
*
|
||||
* @param cert input stream containing the cert
|
||||
* @return builder instance
|
||||
*/
|
||||
Decrypt verifyWithCert(InputStream cert)
|
||||
throws SOPGPException.BadData,
|
||||
IOException;
|
||||
|
||||
/**
|
||||
* Tries to decrypt with the given session key.
|
||||
*
|
||||
* @param sessionKey session key
|
||||
* @return builder instance
|
||||
*/
|
||||
Decrypt withSessionKey(SessionKey sessionKey)
|
||||
throws SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Tries to decrypt with the given password.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
*/
|
||||
Decrypt withPassword(String password)
|
||||
throws SOPGPException.PasswordNotHumanReadable,
|
||||
SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Adds the decryption key.
|
||||
*
|
||||
* @param key input stream containing the key
|
||||
* @return builder instance
|
||||
*/
|
||||
Decrypt withKey(InputStream key)
|
||||
throws SOPGPException.KeyIsProtected,
|
||||
SOPGPException.BadData,
|
||||
SOPGPException.UnsupportedAsymmetricAlgo;
|
||||
|
||||
/**
|
||||
* Decrypts the given ciphertext, returning verification results and plaintext.
|
||||
* @param ciphertext ciphertext
|
||||
* @return ready with result
|
||||
*/
|
||||
ReadyWithResult<DecryptionResult> ciphertext(InputStream ciphertext)
|
||||
throws SOPGPException.BadData, SOPGPException.MissingArg, SOPGPException.CannotDecrypt;
|
||||
}
|
83
sop-java/src/main/java/sop/operation/Encrypt.java
Normal file
83
sop-java/src/main/java/sop/operation/Encrypt.java
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.operation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import sop.Ready;
|
||||
import sop.enums.EncryptAs;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public interface Encrypt {
|
||||
|
||||
/**
|
||||
* Disable ASCII armor encoding.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
Encrypt noArmor();
|
||||
|
||||
/**
|
||||
* Sets encryption mode.
|
||||
*
|
||||
* @param mode mode
|
||||
* @return builder instance
|
||||
*/
|
||||
Encrypt mode(EncryptAs mode)
|
||||
throws SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Adds the signer key.
|
||||
*
|
||||
* @param key input stream containing the encoded signer key
|
||||
* @return builder instance
|
||||
*/
|
||||
Encrypt signWith(InputStream key)
|
||||
throws SOPGPException.KeyIsProtected,
|
||||
SOPGPException.CertCannotSign,
|
||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
||||
SOPGPException.BadData;
|
||||
|
||||
/**
|
||||
* Encrypt with the given password.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
*/
|
||||
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
|
||||
*/
|
||||
Encrypt withCert(InputStream cert)
|
||||
throws SOPGPException.CertCannotEncrypt,
|
||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
||||
SOPGPException.BadData;
|
||||
|
||||
/**
|
||||
* Encrypt the given data yielding the ciphertext.
|
||||
* @param plaintext plaintext
|
||||
* @return input stream containing the ciphertext
|
||||
*/
|
||||
Ready plaintext(InputStream plaintext)
|
||||
throws IOException;
|
||||
}
|
40
sop-java/src/main/java/sop/operation/ExtractCert.java
Normal file
40
sop-java/src/main/java/sop/operation/ExtractCert.java
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.operation;
|
||||
|
||||
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 from the provided key.
|
||||
*
|
||||
* @param keyInputStream input stream containing the encoding of an OpenPGP key
|
||||
* @return input stream containing the encoding of the keys cert
|
||||
*/
|
||||
Ready key(InputStream keyInputStream) throws IOException, SOPGPException.BadData;
|
||||
}
|
47
sop-java/src/main/java/sop/operation/GenerateKey.java
Normal file
47
sop-java/src/main/java/sop/operation/GenerateKey.java
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.operation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import sop.Ready;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* Generate the OpenPGP key and return it encoded as an {@link InputStream}.
|
||||
*
|
||||
* @return key
|
||||
*/
|
||||
Ready generate() throws SOPGPException.MissingArg, SOPGPException.UnsupportedAsymmetricAlgo, IOException;
|
||||
}
|
57
sop-java/src/main/java/sop/operation/Sign.java
Normal file
57
sop-java/src/main/java/sop/operation/Sign.java
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.operation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import sop.Ready;
|
||||
import sop.enums.SignAs;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public interface Sign {
|
||||
|
||||
/**
|
||||
* Disable ASCII armor encoding.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
Sign noArmor();
|
||||
|
||||
/**
|
||||
* Sets the signature mode.
|
||||
*
|
||||
* @param mode signature mode
|
||||
* @return builder instance
|
||||
*/
|
||||
Sign mode(SignAs mode) throws SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Adds the signer key.
|
||||
*
|
||||
* @param key input stream containing encoded key
|
||||
* @return builder instance
|
||||
*/
|
||||
Sign key(InputStream key) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException;
|
||||
|
||||
/**
|
||||
* Signs data.
|
||||
*
|
||||
* @param data input stream containing data
|
||||
* @return ready
|
||||
*/
|
||||
Ready data(InputStream data) throws IOException, SOPGPException.ExpectedText;
|
||||
}
|
57
sop-java/src/main/java/sop/operation/Verify.java
Normal file
57
sop-java/src/main/java/sop/operation/Verify.java
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.operation;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public interface Verify extends VerifySignatures {
|
||||
|
||||
/**
|
||||
* Makes the SOP implementation consider signatures before this date invalid.
|
||||
*
|
||||
* @param timestamp timestamp
|
||||
* @return builder instance
|
||||
*/
|
||||
Verify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Makes the SOP implementation consider signatures after this date invalid.
|
||||
*
|
||||
* @param timestamp timestamp
|
||||
* @return builder instance
|
||||
*/
|
||||
Verify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Adds the verification cert.
|
||||
*
|
||||
* @param cert input stream containing the encoded cert
|
||||
* @return builder instance
|
||||
*/
|
||||
Verify cert(InputStream cert) throws SOPGPException.BadData;
|
||||
|
||||
/**
|
||||
* Provides the signatures.
|
||||
* @param signatures input stream containing encoded, detached signatures.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
VerifySignatures signatures(InputStream signatures) throws SOPGPException.BadData;
|
||||
|
||||
}
|
28
sop-java/src/main/java/sop/operation/VerifySignatures.java
Normal file
28
sop-java/src/main/java/sop/operation/VerifySignatures.java
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.operation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import sop.Verification;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public interface VerifySignatures {
|
||||
|
||||
List<Verification> data(InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData;
|
||||
}
|
33
sop-java/src/main/java/sop/operation/Version.java
Normal file
33
sop-java/src/main/java/sop/operation/Version.java
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2021 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 sop.operation;
|
||||
|
||||
public interface Version {
|
||||
|
||||
/**
|
||||
* Return the implementations name.
|
||||
*
|
||||
* @return implementation name
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Return the implementations version string.
|
||||
*
|
||||
* @return version string
|
||||
*/
|
||||
String getVersion();
|
||||
}
|
21
sop-java/src/main/java/sop/operation/package-info.java
Normal file
21
sop-java/src/main/java/sop/operation/package-info.java
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stateless OpenPGP Interface for Java.
|
||||
* Different cryptographic operations.
|
||||
*/
|
||||
package sop.operation;
|
20
sop-java/src/main/java/sop/package-info.java
Normal file
20
sop-java/src/main/java/sop/package-info.java
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stateless OpenPGP Interface for Java.
|
||||
*/
|
||||
package sop;
|
57
sop-java/src/main/java/sop/util/HexUtil.java
Normal file
57
sop-java/src/main/java/sop/util/HexUtil.java
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub, @maybeWeCouldStealAVan, @Dave L.
|
||||
*
|
||||
* 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 sop.util;
|
||||
|
||||
public class HexUtil {
|
||||
|
||||
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
|
||||
|
||||
/**
|
||||
* Encode a byte array to a hex string.
|
||||
*
|
||||
* @see <a href="https://stackoverflow.com/a/9855338">
|
||||
* How to convert a byte array to a hex string in Java?</a>
|
||||
* @param bytes
|
||||
* @return
|
||||
*/
|
||||
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 <a href="https://stackoverflow.com/a/140861">
|
||||
* Convert a string representation of a hex dump to a byte array using Java?</a>
|
||||
* @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;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue