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
|
@ -27,7 +27,7 @@ allprojects {
|
||||||
apply plugin: 'checkstyle'
|
apply plugin: 'checkstyle'
|
||||||
|
|
||||||
// For non-sop modules, enable android api compatibility check
|
// 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
|
// animalsniffer
|
||||||
apply plugin: 'ru.vyarus.animalsniffer'
|
apply plugin: 'ru.vyarus.animalsniffer'
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
@ -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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.sop;
|
package org.pgpainless.cli;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
|
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
||||||
|
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import picocli.CommandLine;
|
|
||||||
|
|
||||||
public class ExitCodeTest {
|
public class ExitCodeTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ExpectSystemExitWithStatus(69)
|
||||||
public void testUnknownCommand_69() {
|
public void testUnknownCommand_69() {
|
||||||
assertEquals(69, new CommandLine(new PGPainlessCLI()).execute("generate-kex"));
|
PGPainlessCLI.main(new String[] {"generate-kex"});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ExpectSystemExitWithStatus(37)
|
||||||
public void testCommandWithUnknownOption_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
|
@Test
|
||||||
public void successfulVersion_0 () {
|
@FailOnSystemExit
|
||||||
assertEquals(0, new CommandLine(new PGPainlessCLI()).execute("version"));
|
public void successfulExecutionDoesNotTerminateJVM() {
|
||||||
|
PGPainlessCLI.main(new String[] {"version"});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.sop;
|
package org.pgpainless.cli;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.assertArrayEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
@ -28,6 +28,7 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
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.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
import org.pgpainless.sop.PGPainlessCLI;
|
import org.pgpainless.cli.PGPainlessCLI;
|
||||||
import picocli.CommandLine;
|
|
||||||
|
|
||||||
public class ArmorTest {
|
public class ArmorTest {
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@ public class ArmorTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@FailOnSystemExit
|
||||||
public void armorSecretKey() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
public void armorSecretKey() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
||||||
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
||||||
.modernKeyRing("alice@pgpainless.org", null);
|
.modernKeyRing("alice@pgpainless.org", null);
|
||||||
|
@ -61,13 +62,14 @@ public class ArmorTest {
|
||||||
System.setIn(new ByteArrayInputStream(bytes));
|
System.setIn(new ByteArrayInputStream(bytes));
|
||||||
ByteArrayOutputStream armorOut = new ByteArrayOutputStream();
|
ByteArrayOutputStream armorOut = new ByteArrayOutputStream();
|
||||||
System.setOut(new PrintStream(armorOut));
|
System.setOut(new PrintStream(armorOut));
|
||||||
new CommandLine(new PGPainlessCLI()).execute("armor");
|
PGPainlessCLI.execute("armor");
|
||||||
|
|
||||||
PGPSecretKeyRing armored = PGPainless.readKeyRing().secretKeyRing(armorOut.toString());
|
PGPSecretKeyRing armored = PGPainless.readKeyRing().secretKeyRing(armorOut.toString());
|
||||||
assertArrayEquals(secretKey.getEncoded(), armored.getEncoded());
|
assertArrayEquals(secretKey.getEncoded(), armored.getEncoded());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@FailOnSystemExit
|
||||||
public void armorPublicKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
public void armorPublicKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||||
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
||||||
.modernKeyRing("alice@pgpainless.org", null);
|
.modernKeyRing("alice@pgpainless.org", null);
|
||||||
|
@ -77,20 +79,21 @@ public class ArmorTest {
|
||||||
System.setIn(new ByteArrayInputStream(bytes));
|
System.setIn(new ByteArrayInputStream(bytes));
|
||||||
ByteArrayOutputStream armorOut = new ByteArrayOutputStream();
|
ByteArrayOutputStream armorOut = new ByteArrayOutputStream();
|
||||||
System.setOut(new PrintStream(armorOut));
|
System.setOut(new PrintStream(armorOut));
|
||||||
new CommandLine(new PGPainlessCLI()).execute("armor");
|
PGPainlessCLI.execute("armor");
|
||||||
|
|
||||||
PGPPublicKeyRing armored = PGPainless.readKeyRing().publicKeyRing(armorOut.toString());
|
PGPPublicKeyRing armored = PGPainless.readKeyRing().publicKeyRing(armorOut.toString());
|
||||||
assertArrayEquals(publicKey.getEncoded(), armored.getEncoded());
|
assertArrayEquals(publicKey.getEncoded(), armored.getEncoded());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@FailOnSystemExit
|
||||||
public void armorMessage() {
|
public void armorMessage() {
|
||||||
String message = "Hello, World!\n";
|
String message = "Hello, World!\n";
|
||||||
|
|
||||||
System.setIn(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)));
|
System.setIn(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)));
|
||||||
ByteArrayOutputStream armorOut = new ByteArrayOutputStream();
|
ByteArrayOutputStream armorOut = new ByteArrayOutputStream();
|
||||||
System.setOut(new PrintStream(armorOut));
|
System.setOut(new PrintStream(armorOut));
|
||||||
new CommandLine(new PGPainlessCLI()).execute("armor");
|
PGPainlessCLI.execute("armor");
|
||||||
|
|
||||||
String armored = armorOut.toString();
|
String armored = armorOut.toString();
|
||||||
|
|
||||||
|
@ -99,6 +102,7 @@ public class ArmorTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@FailOnSystemExit
|
||||||
public void doesNotNestArmorByDefault() {
|
public void doesNotNestArmorByDefault() {
|
||||||
String armored = "-----BEGIN PGP MESSAGE-----\n" +
|
String armored = "-----BEGIN PGP MESSAGE-----\n" +
|
||||||
"Version: BCPG v1.69\n" +
|
"Version: BCPG v1.69\n" +
|
||||||
|
@ -110,12 +114,13 @@ public class ArmorTest {
|
||||||
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
System.setOut(new PrintStream(out));
|
System.setOut(new PrintStream(out));
|
||||||
new CommandLine(new PGPainlessCLI()).execute("armor");
|
PGPainlessCLI.execute("armor");
|
||||||
|
|
||||||
assertEquals(armored, out.toString());
|
assertEquals(armored, out.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@FailOnSystemExit
|
||||||
public void testAllowNested() {
|
public void testAllowNested() {
|
||||||
String armored = "-----BEGIN PGP MESSAGE-----\n" +
|
String armored = "-----BEGIN PGP MESSAGE-----\n" +
|
||||||
"Version: BCPG v1.69\n" +
|
"Version: BCPG v1.69\n" +
|
||||||
|
@ -127,7 +132,7 @@ public class ArmorTest {
|
||||||
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
System.setOut(new PrintStream(out));
|
System.setOut(new PrintStream(out));
|
||||||
new CommandLine(new PGPainlessCLI()).execute("armor", "--allow-nested");
|
PGPainlessCLI.execute("armor", "--allow-nested");
|
||||||
|
|
||||||
assertNotEquals(armored, out.toString());
|
assertNotEquals(armored, out.toString());
|
||||||
assertTrue(out.toString().contains(
|
assertTrue(out.toString().contains(
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.assertArrayEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
@ -26,6 +26,7 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
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.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
import org.pgpainless.sop.PGPainlessCLI;
|
import org.pgpainless.cli.PGPainlessCLI;
|
||||||
import picocli.CommandLine;
|
|
||||||
|
|
||||||
public class DearmorTest {
|
public class DearmorTest {
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@ public class DearmorTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@FailOnSystemExit
|
||||||
public void dearmorSecretKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
public void dearmorSecretKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||||
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
||||||
.modernKeyRing("alice@pgpainless.org", null);
|
.modernKeyRing("alice@pgpainless.org", null);
|
||||||
|
@ -59,13 +60,14 @@ public class DearmorTest {
|
||||||
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
System.setOut(new PrintStream(out));
|
System.setOut(new PrintStream(out));
|
||||||
new CommandLine(new PGPainlessCLI()).execute("dearmor");
|
PGPainlessCLI.execute("dearmor");
|
||||||
|
|
||||||
assertArrayEquals(secretKey.getEncoded(), out.toByteArray());
|
assertArrayEquals(secretKey.getEncoded(), out.toByteArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@FailOnSystemExit
|
||||||
public void dearmorCertificate() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
public void dearmorCertificate() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||||
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
||||||
.modernKeyRing("alice@pgpainless.org", null);
|
.modernKeyRing("alice@pgpainless.org", null);
|
||||||
|
@ -75,12 +77,13 @@ public class DearmorTest {
|
||||||
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
System.setOut(new PrintStream(out));
|
System.setOut(new PrintStream(out));
|
||||||
new CommandLine(new PGPainlessCLI()).execute("dearmor");
|
PGPainlessCLI.execute("dearmor");
|
||||||
|
|
||||||
assertArrayEquals(certificate.getEncoded(), out.toByteArray());
|
assertArrayEquals(certificate.getEncoded(), out.toByteArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@FailOnSystemExit
|
||||||
public void dearmorMessage() {
|
public void dearmorMessage() {
|
||||||
String armored = "-----BEGIN PGP MESSAGE-----\n" +
|
String armored = "-----BEGIN PGP MESSAGE-----\n" +
|
||||||
"Version: BCPG v1.69\n" +
|
"Version: BCPG v1.69\n" +
|
||||||
|
@ -92,7 +95,7 @@ public class DearmorTest {
|
||||||
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
System.setOut(new PrintStream(out));
|
System.setOut(new PrintStream(out));
|
||||||
new CommandLine(new PGPainlessCLI()).execute("dearmor");
|
PGPainlessCLI.execute("dearmor");
|
||||||
|
|
||||||
assertEquals("Hello, World\n", out.toString());
|
assertEquals("Hello, World\n", out.toString());
|
||||||
}
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
@ -28,12 +28,12 @@ import java.io.OutputStream;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||||
import org.junit.jupiter.api.AfterAll;
|
import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.pgpainless.sop.PGPainlessCLI;
|
import org.pgpainless.cli.PGPainlessCLI;
|
||||||
import org.pgpainless.sop.TestUtils;
|
import org.pgpainless.cli.TestUtils;
|
||||||
import picocli.CommandLine;
|
|
||||||
|
|
||||||
public class EncryptDecryptTest {
|
public class EncryptDecryptTest {
|
||||||
|
|
||||||
|
@ -46,7 +46,8 @@ public class EncryptDecryptTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test() throws IOException {
|
@FailOnSystemExit
|
||||||
|
public void encryptAndDecryptAMessage() throws IOException {
|
||||||
originalSout = System.out;
|
originalSout = System.out;
|
||||||
File julietKeyFile = new File(tempDir, "juliet.key");
|
File julietKeyFile = new File(tempDir, "juliet.key");
|
||||||
assertTrue(julietKeyFile.createNewFile());
|
assertTrue(julietKeyFile.createNewFile());
|
||||||
|
@ -65,27 +66,27 @@ public class EncryptDecryptTest {
|
||||||
|
|
||||||
OutputStream julietKeyOut = new FileOutputStream(julietKeyFile);
|
OutputStream julietKeyOut = new FileOutputStream(julietKeyFile);
|
||||||
System.setOut(new PrintStream(julietKeyOut));
|
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();
|
julietKeyOut.close();
|
||||||
|
|
||||||
FileInputStream julietKeyIn = new FileInputStream(julietKeyFile);
|
FileInputStream julietKeyIn = new FileInputStream(julietKeyFile);
|
||||||
System.setIn(julietKeyIn);
|
System.setIn(julietKeyIn);
|
||||||
OutputStream julietCertOut = new FileOutputStream(julietCertFile);
|
OutputStream julietCertOut = new FileOutputStream(julietCertFile);
|
||||||
System.setOut(new PrintStream(julietCertOut));
|
System.setOut(new PrintStream(julietCertOut));
|
||||||
new CommandLine(new PGPainlessCLI()).execute("extract-cert");
|
PGPainlessCLI.execute("extract-cert");
|
||||||
julietKeyIn.close();
|
julietKeyIn.close();
|
||||||
julietCertOut.close();
|
julietCertOut.close();
|
||||||
|
|
||||||
OutputStream romeoKeyOut = new FileOutputStream(romeoKeyFile);
|
OutputStream romeoKeyOut = new FileOutputStream(romeoKeyFile);
|
||||||
System.setOut(new PrintStream(romeoKeyOut));
|
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();
|
romeoKeyOut.close();
|
||||||
|
|
||||||
FileInputStream romeoKeyIn = new FileInputStream(romeoKeyFile);
|
FileInputStream romeoKeyIn = new FileInputStream(romeoKeyFile);
|
||||||
System.setIn(romeoKeyIn);
|
System.setIn(romeoKeyIn);
|
||||||
OutputStream romeoCertOut = new FileOutputStream(romeoCertFile);
|
OutputStream romeoCertOut = new FileOutputStream(romeoCertFile);
|
||||||
System.setOut(new PrintStream(romeoCertOut));
|
System.setOut(new PrintStream(romeoCertOut));
|
||||||
new CommandLine(new PGPainlessCLI()).execute("extract-cert");
|
PGPainlessCLI.execute("extract-cert");
|
||||||
romeoKeyIn.close();
|
romeoKeyIn.close();
|
||||||
romeoCertOut.close();
|
romeoCertOut.close();
|
||||||
|
|
||||||
|
@ -94,7 +95,7 @@ public class EncryptDecryptTest {
|
||||||
System.setIn(msgIn);
|
System.setIn(msgIn);
|
||||||
OutputStream msgAscOut = new FileOutputStream(msgAscFile);
|
OutputStream msgAscOut = new FileOutputStream(msgAscFile);
|
||||||
System.setOut(new PrintStream(msgAscOut));
|
System.setOut(new PrintStream(msgAscOut));
|
||||||
new CommandLine(new PGPainlessCLI()).execute("encrypt",
|
PGPainlessCLI.execute("encrypt",
|
||||||
"--sign-with", romeoKeyFile.getAbsolutePath(),
|
"--sign-with", romeoKeyFile.getAbsolutePath(),
|
||||||
julietCertFile.getAbsolutePath());
|
julietCertFile.getAbsolutePath());
|
||||||
msgAscOut.close();
|
msgAscOut.close();
|
||||||
|
@ -107,7 +108,7 @@ public class EncryptDecryptTest {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
PrintStream pOut = new PrintStream(out);
|
PrintStream pOut = new PrintStream(out);
|
||||||
System.setOut(pOut);
|
System.setOut(pOut);
|
||||||
new CommandLine(new PGPainlessCLI()).execute("decrypt",
|
PGPainlessCLI.execute("decrypt",
|
||||||
"--verify-out", verifyFile.getAbsolutePath(),
|
"--verify-out", verifyFile.getAbsolutePath(),
|
||||||
"--verify-with", romeoCertFile.getAbsolutePath(),
|
"--verify-with", romeoCertFile.getAbsolutePath(),
|
||||||
julietKeyFile.getAbsolutePath());
|
julietKeyFile.getAbsolutePath());
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
@ -25,18 +25,19 @@ import java.io.PrintStream;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.cli.PGPainlessCLI;
|
||||||
import org.pgpainless.key.info.KeyRingInfo;
|
import org.pgpainless.key.info.KeyRingInfo;
|
||||||
import org.pgpainless.sop.PGPainlessCLI;
|
|
||||||
import picocli.CommandLine;
|
|
||||||
|
|
||||||
public class ExtractCertTest {
|
public class ExtractCertTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@FailOnSystemExit
|
||||||
public void testExtractCert() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException {
|
public void testExtractCert() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException {
|
||||||
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
|
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
|
||||||
.simpleEcKeyRing("Juliet Capulet <juliet@capulet.lit>");
|
.simpleEcKeyRing("Juliet Capulet <juliet@capulet.lit>");
|
||||||
|
@ -46,7 +47,7 @@ public class ExtractCertTest {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
System.setOut(new PrintStream(out));
|
System.setOut(new PrintStream(out));
|
||||||
|
|
||||||
new CommandLine(new PGPainlessCLI()).execute("extract-cert");
|
PGPainlessCLI.execute("extract-cert");
|
||||||
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(out.toByteArray());
|
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(out.toByteArray());
|
||||||
KeyRingInfo info = PGPainless.inspectKeyRing(publicKeys);
|
KeyRingInfo info = PGPainless.inspectKeyRing(publicKeys);
|
||||||
assertFalse(info.isSecretKey());
|
assertFalse(info.isSecretKey());
|
|
@ -13,12 +13,12 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.assertArrayEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
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.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -26,15 +26,15 @@ import java.io.IOException;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.cli.PGPainlessCLI;
|
||||||
|
import org.pgpainless.cli.TestUtils;
|
||||||
import org.pgpainless.key.info.KeyRingInfo;
|
import org.pgpainless.key.info.KeyRingInfo;
|
||||||
import org.pgpainless.sop.PGPainlessCLI;
|
|
||||||
import org.pgpainless.sop.TestUtils;
|
|
||||||
import picocli.CommandLine;
|
|
||||||
|
|
||||||
public class GenerateCertTest {
|
public class GenerateCertTest {
|
||||||
|
|
||||||
|
@ -47,10 +47,11 @@ public class GenerateCertTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@FailOnSystemExit
|
||||||
public void testKeyGeneration() throws IOException, PGPException {
|
public void testKeyGeneration() throws IOException, PGPException {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
System.setOut(new PrintStream(out));
|
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());
|
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(out.toByteArray());
|
||||||
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
|
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
|
||||||
|
@ -62,10 +63,11 @@ public class GenerateCertTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@FailOnSystemExit
|
||||||
public void testNoArmor() {
|
public void testNoArmor() {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
System.setOut(new PrintStream(out));
|
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];
|
byte[] outBegin = new byte[37];
|
||||||
System.arraycopy(out.toByteArray(), 0, outBegin, 0, 37);
|
System.arraycopy(out.toByteArray(), 0, outBegin, 0, 37);
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
@ -32,6 +32,7 @@ import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
import com.ginsberg.junit.exit.FailOnSystemExit;
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
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.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.cli.PGPainlessCLI;
|
||||||
|
import org.pgpainless.cli.TestUtils;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
import org.pgpainless.key.info.KeyRingInfo;
|
import org.pgpainless.key.info.KeyRingInfo;
|
||||||
import org.pgpainless.key.util.KeyRingUtils;
|
import org.pgpainless.key.util.KeyRingUtils;
|
||||||
import org.pgpainless.sop.PGPainlessCLI;
|
|
||||||
import org.pgpainless.sop.TestUtils;
|
|
||||||
import picocli.CommandLine;
|
|
||||||
|
|
||||||
public class SignVerifyTest {
|
public class SignVerifyTest {
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@ public class SignVerifyTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@FailOnSystemExit
|
||||||
public void testSignatureCreationAndVerification() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
public void testSignatureCreationAndVerification() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||||
originalSout = System.out;
|
originalSout = System.out;
|
||||||
InputStream originalIn = System.in;
|
InputStream originalIn = System.in;
|
||||||
|
@ -95,7 +96,7 @@ public class SignVerifyTest {
|
||||||
assertTrue(sigFile.createNewFile());
|
assertTrue(sigFile.createNewFile());
|
||||||
FileOutputStream sigOut = new FileOutputStream(sigFile);
|
FileOutputStream sigOut = new FileOutputStream(sigFile);
|
||||||
System.setOut(new PrintStream(sigOut));
|
System.setOut(new PrintStream(sigOut));
|
||||||
new CommandLine(new PGPainlessCLI()).execute("sign", "--armor", aliceKeyFile.getAbsolutePath());
|
PGPainlessCLI.execute("sign", "--armor", aliceKeyFile.getAbsolutePath());
|
||||||
sigOut.close();
|
sigOut.close();
|
||||||
|
|
||||||
// verify test data signature
|
// verify test data signature
|
||||||
|
@ -103,7 +104,7 @@ public class SignVerifyTest {
|
||||||
System.setOut(new PrintStream(verifyOut));
|
System.setOut(new PrintStream(verifyOut));
|
||||||
dataIn = new FileInputStream(dataFile);
|
dataIn = new FileInputStream(dataFile);
|
||||||
System.setIn(dataIn);
|
System.setIn(dataIn);
|
||||||
new CommandLine(new PGPainlessCLI()).execute("verify", sigFile.getAbsolutePath(), aliceCertFile.getAbsolutePath());
|
PGPainlessCLI.execute("verify", sigFile.getAbsolutePath(), aliceCertFile.getAbsolutePath());
|
||||||
dataIn.close();
|
dataIn.close();
|
||||||
|
|
||||||
// Test verification output
|
// Test verification output
|
||||||
|
@ -113,8 +114,8 @@ public class SignVerifyTest {
|
||||||
String[] split = verification.split(" ");
|
String[] split = verification.split(" ");
|
||||||
OpenPgpV4Fingerprint primaryKeyFingerprint = new OpenPgpV4Fingerprint(aliceKeys);
|
OpenPgpV4Fingerprint primaryKeyFingerprint = new OpenPgpV4Fingerprint(aliceKeys);
|
||||||
OpenPgpV4Fingerprint signingKeyFingerprint = new OpenPgpV4Fingerprint(new KeyRingInfo(alicePub, new Date()).getSigningSubkeys().get(0));
|
OpenPgpV4Fingerprint signingKeyFingerprint = new OpenPgpV4Fingerprint(new KeyRingInfo(alicePub, new Date()).getSigningSubkeys().get(0));
|
||||||
assertEquals(signingKeyFingerprint.toString(), split[1]);
|
assertEquals(signingKeyFingerprint.toString(), split[1].trim());
|
||||||
assertEquals(primaryKeyFingerprint.toString(), split[2]);
|
assertEquals(primaryKeyFingerprint.toString(), split[2].trim());
|
||||||
|
|
||||||
System.setIn(originalIn);
|
System.setIn(originalIn);
|
||||||
}
|
}
|
|
@ -16,6 +16,7 @@
|
||||||
package org.pgpainless.decryption_verification;
|
package org.pgpainless.decryption_verification;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -76,8 +77,10 @@ public final class DecryptionStreamFactory {
|
||||||
private final ConsumerOptions options;
|
private final ConsumerOptions options;
|
||||||
|
|
||||||
private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
|
private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
|
||||||
private static final PGPContentVerifierBuilderProvider verifierBuilderProvider = ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider();
|
private static final PGPContentVerifierBuilderProvider verifierBuilderProvider =
|
||||||
private static final KeyFingerPrintCalculator keyFingerprintCalculator = ImplementationFactory.getInstance().getKeyFingerprintCalculator();
|
ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider();
|
||||||
|
private static final KeyFingerPrintCalculator keyFingerprintCalculator =
|
||||||
|
ImplementationFactory.getInstance().getKeyFingerprintCalculator();
|
||||||
private final Map<OpenPgpV4Fingerprint, OnePassSignature> verifiableOnePassSignatures = new HashMap<>();
|
private final Map<OpenPgpV4Fingerprint, OnePassSignature> verifiableOnePassSignatures = new HashMap<>();
|
||||||
private final List<IntegrityProtectedInputStream> integrityProtectedStreams = new ArrayList<>();
|
private final List<IntegrityProtectedInputStream> integrityProtectedStreams = new ArrayList<>();
|
||||||
|
|
||||||
|
@ -109,11 +112,23 @@ public final class DecryptionStreamFactory {
|
||||||
try {
|
try {
|
||||||
// Parse OpenPGP message
|
// Parse OpenPGP message
|
||||||
inputStream = factory.processPGPPackets(objectFactory, 1);
|
inputStream = factory.processPGPPackets(objectFactory, 1);
|
||||||
} catch (MissingLiteralDataException e) {
|
} catch (EOFException e) {
|
||||||
// Not an OpenPGP message. Reset the buffered stream to parse the message as arbitrary binary data
|
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.
|
// to allow for detached signature verification.
|
||||||
bufferedIn.reset();
|
bufferedIn.reset();
|
||||||
inputStream = bufferedIn;
|
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);
|
return new DecryptionStream(inputStream, factory.resultBuilder, factory.integrityProtectedStreams);
|
||||||
|
@ -146,7 +161,9 @@ public final class DecryptionStreamFactory {
|
||||||
throws PGPException, IOException {
|
throws PGPException, IOException {
|
||||||
LOGGER.log(LEVEL, "Encountered PGPEncryptedDataList");
|
LOGGER.log(LEVEL, "Encountered PGPEncryptedDataList");
|
||||||
InputStream decryptedDataStream = decrypt(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)
|
private InputStream processPGPCompressedData(PGPCompressedData pgpCompressedData, int depth)
|
||||||
|
@ -155,8 +172,9 @@ public final class DecryptionStreamFactory {
|
||||||
LOGGER.log(LEVEL, "Encountered PGPCompressedData: " + compressionAlgorithm);
|
LOGGER.log(LEVEL, "Encountered PGPCompressedData: " + compressionAlgorithm);
|
||||||
resultBuilder.setCompressionAlgorithm(compressionAlgorithm);
|
resultBuilder.setCompressionAlgorithm(compressionAlgorithm);
|
||||||
|
|
||||||
InputStream dataStream = pgpCompressedData.getDataStream();
|
InputStream inflatedDataStream = pgpCompressedData.getDataStream();
|
||||||
PGPObjectFactory objectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(dataStream), keyFingerprintCalculator);
|
InputStream decodedDataStream = PGPUtil.getDecoderStream(inflatedDataStream);
|
||||||
|
PGPObjectFactory objectFactory = new PGPObjectFactory(decodedDataStream, keyFingerprintCalculator);
|
||||||
|
|
||||||
return processPGPPackets(objectFactory, ++depth);
|
return processPGPPackets(objectFactory, ++depth);
|
||||||
}
|
}
|
||||||
|
@ -171,11 +189,10 @@ public final class DecryptionStreamFactory {
|
||||||
private InputStream processPGPLiteralData(@Nonnull PGPObjectFactory objectFactory, PGPLiteralData pgpLiteralData) {
|
private InputStream processPGPLiteralData(@Nonnull PGPObjectFactory objectFactory, PGPLiteralData pgpLiteralData) {
|
||||||
LOGGER.log(LEVEL, "Found PGPLiteralData");
|
LOGGER.log(LEVEL, "Found PGPLiteralData");
|
||||||
InputStream literalDataInputStream = pgpLiteralData.getInputStream();
|
InputStream literalDataInputStream = pgpLiteralData.getInputStream();
|
||||||
OpenPgpMetadata.FileInfo fileInfo = new OpenPgpMetadata.FileInfo(
|
|
||||||
pgpLiteralData.getFileName(),
|
resultBuilder.setFileName(pgpLiteralData.getFileName())
|
||||||
pgpLiteralData.getModificationTime(),
|
.setModificationDate(pgpLiteralData.getModificationTime())
|
||||||
StreamEncoding.fromCode(pgpLiteralData.getFormat()));
|
.setFileEncoding(StreamEncoding.fromCode(pgpLiteralData.getFormat()));
|
||||||
resultBuilder.setFileInfo(fileInfo);
|
|
||||||
|
|
||||||
if (verifiableOnePassSignatures.isEmpty()) {
|
if (verifiableOnePassSignatures.isEmpty()) {
|
||||||
LOGGER.log(LEVEL, "No OnePassSignatures found -> We are done");
|
LOGGER.log(LEVEL, "No OnePassSignatures found -> We are done");
|
||||||
|
@ -226,9 +243,6 @@ public final class DecryptionStreamFactory {
|
||||||
|
|
||||||
// data is public key encrypted
|
// data is public key encrypted
|
||||||
else if (encryptedData instanceof PGPPublicKeyEncryptedData) {
|
else if (encryptedData instanceof PGPPublicKeyEncryptedData) {
|
||||||
if (options.getDecryptionKeys().isEmpty()) {
|
|
||||||
|
|
||||||
}
|
|
||||||
PGPPublicKeyEncryptedData publicKeyEncryptedData = (PGPPublicKeyEncryptedData) encryptedData;
|
PGPPublicKeyEncryptedData publicKeyEncryptedData = (PGPPublicKeyEncryptedData) encryptedData;
|
||||||
long keyId = publicKeyEncryptedData.getKeyID();
|
long keyId = publicKeyEncryptedData.getKeyID();
|
||||||
if (!options.getDecryptionKeys().isEmpty()) {
|
if (!options.getDecryptionKeys().isEmpty()) {
|
||||||
|
|
|
@ -24,6 +24,8 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPLiteralData;
|
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
@ -44,7 +46,9 @@ public class OpenPgpMetadata {
|
||||||
private final List<DetachedSignature> detachedSignatures;
|
private final List<DetachedSignature> detachedSignatures;
|
||||||
private final SymmetricKeyAlgorithm symmetricKeyAlgorithm;
|
private final SymmetricKeyAlgorithm symmetricKeyAlgorithm;
|
||||||
private final CompressionAlgorithm compressionAlgorithm;
|
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,
|
public OpenPgpMetadata(Set<Long> recipientKeyIds,
|
||||||
SubkeyIdentifier decryptionKey,
|
SubkeyIdentifier decryptionKey,
|
||||||
|
@ -52,7 +56,9 @@ public class OpenPgpMetadata {
|
||||||
CompressionAlgorithm algorithm,
|
CompressionAlgorithm algorithm,
|
||||||
List<OnePassSignature> onePassSignatures,
|
List<OnePassSignature> onePassSignatures,
|
||||||
List<DetachedSignature> detachedSignatures,
|
List<DetachedSignature> detachedSignatures,
|
||||||
FileInfo fileInfo) {
|
String fileName,
|
||||||
|
Date modificationDate,
|
||||||
|
StreamEncoding fileEncoding) {
|
||||||
|
|
||||||
this.recipientKeyIds = Collections.unmodifiableSet(recipientKeyIds);
|
this.recipientKeyIds = Collections.unmodifiableSet(recipientKeyIds);
|
||||||
this.decryptionKey = decryptionKey;
|
this.decryptionKey = decryptionKey;
|
||||||
|
@ -60,7 +66,9 @@ public class OpenPgpMetadata {
|
||||||
this.compressionAlgorithm = algorithm;
|
this.compressionAlgorithm = algorithm;
|
||||||
this.detachedSignatures = Collections.unmodifiableList(detachedSignatures);
|
this.detachedSignatures = Collections.unmodifiableList(detachedSignatures);
|
||||||
this.onePassSignatures = Collections.unmodifiableList(onePassSignatures);
|
this.onePassSignatures = Collections.unmodifiableList(onePassSignatures);
|
||||||
this.fileInfo = fileInfo;
|
this.fileName = fileName;
|
||||||
|
this.modificationDate = modificationDate;
|
||||||
|
this.fileEncoding = fileEncoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<Long> getRecipientKeyIds() {
|
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() {
|
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 class FileInfo {
|
||||||
public static final String FOR_YOUR_EYES_ONLY = PGPLiteralData.CONSOLE;
|
|
||||||
|
|
||||||
protected final String fileName;
|
protected final String fileName;
|
||||||
protected final Date modificationDate;
|
protected final Date modificationDate;
|
||||||
|
@ -165,22 +220,10 @@ public class OpenPgpMetadata {
|
||||||
this.streamEncoding = streamEncoding;
|
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() {
|
public String getFileName() {
|
||||||
return fileName;
|
return fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isForYourEyesOnly() {
|
|
||||||
return FOR_YOUR_EYES_ONLY.equals(fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getModificationDate() {
|
public Date getModificationDate() {
|
||||||
return modificationDate;
|
return modificationDate;
|
||||||
}
|
}
|
||||||
|
@ -243,7 +286,9 @@ public class OpenPgpMetadata {
|
||||||
private final List<OnePassSignature> onePassSignatures = new ArrayList<>();
|
private final List<OnePassSignature> onePassSignatures = new ArrayList<>();
|
||||||
private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.NULL;
|
private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.NULL;
|
||||||
private CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED;
|
private CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED;
|
||||||
private FileInfo fileInfo;
|
private String fileName;
|
||||||
|
private StreamEncoding fileEncoding;
|
||||||
|
private Date modificationDate;
|
||||||
|
|
||||||
public Builder addRecipientKeyId(Long keyId) {
|
public Builder addRecipientKeyId(Long keyId) {
|
||||||
this.recipientFingerprints.add(keyId);
|
this.recipientFingerprints.add(keyId);
|
||||||
|
@ -269,6 +314,21 @@ public class OpenPgpMetadata {
|
||||||
return this;
|
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) {
|
public void addDetachedSignature(DetachedSignature signature) {
|
||||||
this.detachedSignatures.add(signature);
|
this.detachedSignatures.add(signature);
|
||||||
}
|
}
|
||||||
|
@ -277,15 +337,10 @@ public class OpenPgpMetadata {
|
||||||
this.onePassSignatures.add(onePassSignature);
|
this.onePassSignatures.add(onePassSignature);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setFileInfo(FileInfo fileInfo) {
|
|
||||||
this.fileInfo = fileInfo;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OpenPgpMetadata build() {
|
public OpenPgpMetadata build() {
|
||||||
return new OpenPgpMetadata(recipientFingerprints, decryptionKey,
|
return new OpenPgpMetadata(recipientFingerprints, decryptionKey,
|
||||||
symmetricKeyAlgorithm, compressionAlgorithm,
|
symmetricKeyAlgorithm, compressionAlgorithm,
|
||||||
onePassSignatures, detachedSignatures, fileInfo);
|
onePassSignatures, detachedSignatures, fileName, modificationDate, fileEncoding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,16 @@
|
||||||
package org.pgpainless.encryption_signing;
|
package org.pgpainless.encryption_signing;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||||
|
import org.pgpainless.algorithm.StreamEncoding;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||||
import org.pgpainless.key.SubkeyIdentifier;
|
import org.pgpainless.key.SubkeyIdentifier;
|
||||||
|
@ -33,18 +38,24 @@ public final class EncryptionResult {
|
||||||
|
|
||||||
private final MultiMap<SubkeyIdentifier, PGPSignature> detachedSignatures;
|
private final MultiMap<SubkeyIdentifier, PGPSignature> detachedSignatures;
|
||||||
private final Set<SubkeyIdentifier> recipients;
|
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,
|
private EncryptionResult(SymmetricKeyAlgorithm encryptionAlgorithm,
|
||||||
CompressionAlgorithm compressionAlgorithm,
|
CompressionAlgorithm compressionAlgorithm,
|
||||||
MultiMap<SubkeyIdentifier, PGPSignature> detachedSignatures,
|
MultiMap<SubkeyIdentifier, PGPSignature> detachedSignatures,
|
||||||
Set<SubkeyIdentifier> recipients,
|
Set<SubkeyIdentifier> recipients,
|
||||||
OpenPgpMetadata.FileInfo fileInfo) {
|
String fileName,
|
||||||
|
Date modificationDate,
|
||||||
|
StreamEncoding encoding) {
|
||||||
this.encryptionAlgorithm = encryptionAlgorithm;
|
this.encryptionAlgorithm = encryptionAlgorithm;
|
||||||
this.compressionAlgorithm = compressionAlgorithm;
|
this.compressionAlgorithm = compressionAlgorithm;
|
||||||
this.detachedSignatures = detachedSignatures;
|
this.detachedSignatures = detachedSignatures;
|
||||||
this.recipients = Collections.unmodifiableSet(recipients);
|
this.recipients = Collections.unmodifiableSet(recipients);
|
||||||
this.fileInfo = fileInfo;
|
this.fileName = fileName;
|
||||||
|
this.modificationDate = modificationDate;
|
||||||
|
this.fileEncoding = encoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
|
@ -68,14 +79,37 @@ public final class EncryptionResult {
|
||||||
return recipients;
|
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() {
|
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() {
|
public static Builder builder() {
|
||||||
return new Builder();
|
return new Builder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isForYourEyesOnly() {
|
||||||
|
return PGPLiteralData.CONSOLE.equals(getFileName());
|
||||||
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private SymmetricKeyAlgorithm encryptionAlgorithm;
|
private SymmetricKeyAlgorithm encryptionAlgorithm;
|
||||||
|
@ -83,7 +117,9 @@ public final class EncryptionResult {
|
||||||
|
|
||||||
private final MultiMap<SubkeyIdentifier, PGPSignature> detachedSignatures = new MultiMap<>();
|
private final MultiMap<SubkeyIdentifier, PGPSignature> detachedSignatures = new MultiMap<>();
|
||||||
private Set<SubkeyIdentifier> recipients = new HashSet<>();
|
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) {
|
public Builder setEncryptionAlgorithm(SymmetricKeyAlgorithm encryptionAlgorithm) {
|
||||||
this.encryptionAlgorithm = encryptionAlgorithm;
|
this.encryptionAlgorithm = encryptionAlgorithm;
|
||||||
|
@ -105,8 +141,18 @@ public final class EncryptionResult {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setFileInfo(OpenPgpMetadata.FileInfo fileInfo) {
|
public Builder setFileName(@Nonnull String fileName) {
|
||||||
this.fileInfo = fileInfo;
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,11 +163,9 @@ public final class EncryptionResult {
|
||||||
if (compressionAlgorithm == null) {
|
if (compressionAlgorithm == null) {
|
||||||
throw new IllegalStateException("Compression algorithm not set.");
|
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.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator;
|
||||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
|
||||||
import org.pgpainless.implementation.ImplementationFactory;
|
import org.pgpainless.implementation.ImplementationFactory;
|
||||||
import org.pgpainless.key.SubkeyIdentifier;
|
import org.pgpainless.key.SubkeyIdentifier;
|
||||||
import org.pgpainless.util.ArmoredOutputStreamFactory;
|
import org.pgpainless.util.ArmoredOutputStreamFactory;
|
||||||
|
@ -154,8 +153,9 @@ public final class EncryptionStream extends OutputStream {
|
||||||
new byte[BUFFER_SIZE]);
|
new byte[BUFFER_SIZE]);
|
||||||
outermostStream = literalDataStream;
|
outermostStream = literalDataStream;
|
||||||
|
|
||||||
resultBuilder.setFileInfo(new OpenPgpMetadata.FileInfo(
|
resultBuilder.setFileName(options.getFileName())
|
||||||
options.getFileName(), options.getModificationDate(), options.getEncoding()));
|
.setModificationDate(options.getModificationDate())
|
||||||
|
.setFileEncoding(options.getEncoding());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -256,4 +256,8 @@ public final class EncryptionStream extends OutputStream {
|
||||||
}
|
}
|
||||||
return resultBuilder.build();
|
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.PGPainless;
|
||||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||||
import org.pgpainless.algorithm.HashAlgorithm;
|
import org.pgpainless.algorithm.HashAlgorithm;
|
||||||
|
import org.pgpainless.exception.KeyCannotSignException;
|
||||||
import org.pgpainless.exception.KeyValidationException;
|
import org.pgpainless.exception.KeyValidationException;
|
||||||
import org.pgpainless.implementation.ImplementationFactory;
|
import org.pgpainless.implementation.ImplementationFactory;
|
||||||
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
import org.pgpainless.key.SubkeyIdentifier;
|
import org.pgpainless.key.SubkeyIdentifier;
|
||||||
import org.pgpainless.key.info.KeyRingInfo;
|
import org.pgpainless.key.info.KeyRingInfo;
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
|
import org.pgpainless.key.protection.UnlockSecretKey;
|
||||||
import org.pgpainless.policy.Policy;
|
import org.pgpainless.policy.Policy;
|
||||||
|
|
||||||
public final class SigningOptions {
|
public final class SigningOptions {
|
||||||
|
@ -161,12 +164,12 @@ public final class SigningOptions {
|
||||||
|
|
||||||
List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
|
List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
|
||||||
if (signingPubKeys.isEmpty()) {
|
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) {
|
for (PGPPublicKey signingPubKey : signingPubKeys) {
|
||||||
PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
|
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());
|
Set<HashAlgorithm> hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(userId, signingPubKey.getKeyID());
|
||||||
HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
|
HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
|
||||||
addSigningMethod(secretKey, signingSubkey, hashAlgorithm, signatureType, false);
|
addSigningMethod(secretKey, signingSubkey, hashAlgorithm, signatureType, false);
|
||||||
|
@ -242,7 +245,7 @@ public final class SigningOptions {
|
||||||
|
|
||||||
List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
|
List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
|
||||||
if (signingPubKeys.isEmpty()) {
|
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) {
|
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.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -68,6 +69,23 @@ public class ArmorUtils {
|
||||||
return sb.toString();
|
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 {
|
public static String toAsciiArmoredString(PGPPublicKeyRingCollection publicKeyRings) throws IOException {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for (Iterator<PGPPublicKeyRing> iterator = publicKeyRings.iterator(); iterator.hasNext(); ) {
|
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 {
|
public static String toAsciiArmoredString(InputStream inputStream, MultiMap<String, String> additionalHeaderValues) throws IOException {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
ArmoredOutputStream armor = ArmoredOutputStreamFactory.get(out);
|
ArmoredOutputStream armor = toAsciiArmoredStream(out, additionalHeaderValues);
|
||||||
if (additionalHeaderValues != null) {
|
|
||||||
for (String header : additionalHeaderValues.keySet()) {
|
|
||||||
for (String value : additionalHeaderValues.get(header)) {
|
|
||||||
armor.addHeader(header, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Streams.pipeAll(inputStream, armor);
|
Streams.pipeAll(inputStream, armor);
|
||||||
armor.close();
|
armor.close();
|
||||||
|
|
||||||
return out.toString();
|
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) {
|
public static List<String> getCommendHeaderValues(ArmoredInputStream armor) {
|
||||||
return getArmorHeaderValues(armor, HEADER_COMMENT);
|
return getArmorHeaderValues(armor, HEADER_COMMENT);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,17 @@ package org.junit;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import org.pgpainless.util.DateUtil;
|
||||||
|
|
||||||
public class JUtils {
|
public class JUtils {
|
||||||
|
|
||||||
public static void assertEquals(long a, long b, long delta) {
|
public static void assertEquals(long a, long b, long delta) {
|
||||||
assertTrue(a - delta <= b && a + delta >= b);
|
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 {
|
plugins {
|
||||||
id 'application'
|
id 'java'
|
||||||
|
}
|
||||||
|
|
||||||
|
group 'org.pgpainless'
|
||||||
|
version '0.2.1-SNAPSHOT'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
implementation(project(":pgpainless-core"))
|
implementation(project(":pgpainless-core"))
|
||||||
|
implementation(project(":sop-java"))
|
||||||
|
|
||||||
implementation 'info.picocli:picocli:4.5.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
|
||||||
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
|
||||||
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'
|
|
||||||
}
|
|
||||||
|
|
||||||
mainClassName = 'org.pgpainless.sop.PGPainlessCLI'
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.sop.commands;
|
package org.pgpainless.sop;
|
||||||
|
|
||||||
import static org.pgpainless.sop.Print.print_ln;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import picocli.CommandLine;
|
import sop.operation.Version;
|
||||||
|
|
||||||
@CommandLine.Command(name = "version", description = "Display version information about the tool",
|
public class VersionImpl implements Version {
|
||||||
exitCodeOnInvalidInput = 37)
|
@Override
|
||||||
public class Version implements Runnable {
|
public String getName() {
|
||||||
|
return "PGPainless-SOP";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public String getVersion() {
|
||||||
// See https://stackoverflow.com/a/50119235
|
// See https://stackoverflow.com/a/50119235
|
||||||
String version;
|
String version;
|
||||||
try {
|
try {
|
||||||
|
@ -37,6 +37,6 @@ public class Version implements Runnable {
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
version = "DEVELOPMENT";
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,9 +14,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* PGPainless SOP implementing a Stateless OpenPGP Command Line Interface.
|
* Implementation of the java-sop package using pgpainless-core.
|
||||||
* @see <a href="https://tools.ietf.org/html/draft-dkg-openpgp-stateless-cli-01">
|
|
||||||
* Stateless OpenPGP Command Line Interface
|
|
||||||
* draft-dkg-openpgp-stateless-cli-01</a>
|
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.sop;
|
package org.pgpainless.sop;
|
||||||
|
|
|
@ -15,13 +15,14 @@
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.sop;
|
package org.pgpainless.sop;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import picocli.CommandLine;
|
|
||||||
|
|
||||||
public class DummyTest {
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class VersionTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void dummyTest() {
|
public void assertNameEqualsPGPainless() {
|
||||||
new CommandLine(new PGPainlessCLI()).execute("generate-key", "Ed Snowden <citizen4@lavabit.com>");
|
assertEquals("PGPainless-SOP", new SOPImpl().version().getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
rootProject.name = 'PGPainless'
|
rootProject.name = 'PGPainless'
|
||||||
|
|
||||||
include 'pgpainless-core',
|
include 'pgpainless-core',
|
||||||
'pgpainless-sop'
|
'sop-java',
|
||||||
|
'pgpainless-sop',
|
||||||
|
'sop-java-picocli',
|
||||||
|
'pgpainless-cli'
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.sop.commands;
|
package sop.cli.picocli.commands;
|
||||||
|
|
||||||
import org.bouncycastle.bcpg.ArmoredInputStream;
|
|
||||||
import org.bouncycastle.util.io.Streams;
|
|
||||||
import picocli.CommandLine;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
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",
|
@CommandLine.Command(name = "dearmor",
|
||||||
description = "Remove ASCII Armor from standard input",
|
description = "Remove ASCII Armor from standard input",
|
||||||
exitCodeOnInvalidInput = 37)
|
exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE)
|
||||||
public class Dearmor implements Runnable {
|
public class DearmorCmd implements Runnable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try (ArmoredInputStream in = new ArmoredInputStream(System.in, true)) {
|
try {
|
||||||
Streams.pipeAll(in, System.out);
|
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) {
|
} catch (IOException e) {
|
||||||
err_ln("Data cannot be dearmored.");
|
Print.errln("IO Error.");
|
||||||
err_ln(e.getMessage());
|
Print.trace(e);
|
||||||
System.exit(1);
|
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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.sop.commands;
|
package sop.cli.picocli.commands;
|
||||||
|
|
||||||
import static org.pgpainless.sop.Print.err_ln;
|
|
||||||
import static org.pgpainless.sop.Print.print_ln;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
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 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",
|
@CommandLine.Command(name = "extract-cert",
|
||||||
description = "Extract a public key certificate from a secret key from standard input",
|
description = "Extract a public key certificate from a secret key from standard input",
|
||||||
exitCodeOnInvalidInput = 37)
|
exitCodeOnInvalidInput = 37)
|
||||||
public class ExtractCert implements Runnable {
|
public class ExtractCertCmd implements Runnable {
|
||||||
|
|
||||||
@CommandLine.Option(names = "--no-armor",
|
@CommandLine.Option(names = "--no-armor",
|
||||||
description = "ASCII armor the output",
|
description = "ASCII armor the output",
|
||||||
|
@ -40,15 +36,22 @@ public class ExtractCert implements Runnable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
ExtractCert extractCert = SopCLI.getSop().extractCert();
|
||||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(System.in);
|
if (!armor) {
|
||||||
PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys);
|
extractCert.noArmor();
|
||||||
|
}
|
||||||
|
|
||||||
print_ln(Print.toString(publicKeys, armor));
|
try {
|
||||||
} catch (IOException | PGPException e) {
|
Ready ready = extractCert.key(System.in);
|
||||||
err_ln("Error extracting certificate from keys;");
|
ready.writeTo(System.out);
|
||||||
err_ln(e.getMessage());
|
} catch (IOException e) {
|
||||||
|
Print.errln("IO Error.");
|
||||||
|
Print.trace(e);
|
||||||
System.exit(1);
|
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.
|
* 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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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;
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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 New Issue