179 lines
6.6 KiB
Java
179 lines
6.6 KiB
Java
/*
|
|
* 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);
|
|
}
|
|
}
|
|
}
|
|
}
|