// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0

package org.pgpainless.cli.commands;

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.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;

import com.ginsberg.junit.exit.FailOnSystemExit;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.KeyFlag;
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.slf4j.LoggerFactory;
import sop.exception.SOPGPException;

public class RoundTripEncryptDecryptCmdTest extends CLITest {

    public RoundTripEncryptDecryptCmdTest() {
        super(LoggerFactory.getLogger(RoundTripEncryptDecryptCmdTest.class));
    }

    private static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
            "Version: PGPainless\n" +
            "Comment: A2EC 077F C977 E15D D799  EFF9 2C0D 3C12 3CF5 1C08\n" +
            "Comment: Alice <alice@pgpainless.org>\n" +
            "\n" +
            "lFgEY2veRhYJKwYBBAHaRw8BAQdAeJYBoCcnGPQ3nchyyBrWQ83q3hqJnfZn2mqh\n" +
            "d1M7WwsAAP0R1ELnfdJhXcfjaYPLHzwy1i34FxP5g3tvdgg9Q7VmchActBxBbGlj\n" +
            "ZSA8YWxpY2VAcGdwYWlubGVzcy5vcmc+iI8EExYKAEEFAmNr3kYJECwNPBI89RwI\n" +
            "FiEEouwHf8l34V3Xme/5LA08Ejz1HAgCngECmwEFFgIDAQAECwkIBwUVCgkICwKZ\n" +
            "AQAAe6YA/2sO483Vi2Fgs4ejv8FykyO96IVrMoYhw3Od4LyWEyDfAQDi15LxJJm6\n" +
            "T2sXdENVigdwDJiELxjOtbmivuJutxkWCJxdBGNr3kYSCisGAQQBl1UBBQEBB0CS\n" +
            "zXjySHqlicxG3QlrVeTIqwKitL1sWsx0MCDmT1C8dAMBCAcAAP9VNkfMQvYAlYSP\n" +
            "aYEkwEOc8/XpiloVKtPzxwVCPlXFeBDCiHUEGBYKAB0FAmNr3kYCngECmwwFFgID\n" +
            "AQAECwkIBwUVCgkICwAKCRAsDTwSPPUcCOT4AQDZcN5a/e8Qr+LNBIyXXLgJWGsL\n" +
            "59nsKHBbDURnxbEnMQEAybS8u+Rsb82yW4CfaA4CLRTC3eDc5Y4QwYWzLogWNwic\n" +
            "WARja95GFgkrBgEEAdpHDwEBB0DcdwQufWLq6ASku4JWBBd9JplRVhK0cXWuTE73\n" +
            "uWltuwABAI0bVQXvgDnxTs6kUO7JIWtokM5lI/1bfG4L1YOfnXIgD7CI1QQYFgoA\n" +
            "fQUCY2veRgKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNr3kYACgkQ\n" +
            "7NC/hj9lyaWVAwEA3ze1LCi1reGfB5tS3Au6A8aalyk4UV0iVOXxwV5r+E4BAJGz\n" +
            "ZMFF/iQ/rOcSAsHPp4ggezZALDIkT2Hrn6iLDdsLAAoJECwNPBI89RwIuBIBAMxG\n" +
            "u/s4maOFozcO4JoCZTsLHGy70SG6UuVQjK0EyJJ1APoDEfK+qTlC7/FoijMA6Ew9\n" +
            "aesZ2IHgpwA7jlyHSgwLDw==\n" +
            "=H3HU\n" +
            "-----END PGP PRIVATE KEY BLOCK-----";
    private static final String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
            "Version: PGPainless\n" +
            "Comment: A2EC 077F C977 E15D D799  EFF9 2C0D 3C12 3CF5 1C08\n" +
            "Comment: Alice <alice@pgpainless.org>\n" +
            "\n" +
            "mDMEY2veRhYJKwYBBAHaRw8BAQdAeJYBoCcnGPQ3nchyyBrWQ83q3hqJnfZn2mqh\n" +
            "d1M7Wwu0HEFsaWNlIDxhbGljZUBwZ3BhaW5sZXNzLm9yZz6IjwQTFgoAQQUCY2ve\n" +
            "RgkQLA08Ejz1HAgWIQSi7Ad/yXfhXdeZ7/ksDTwSPPUcCAKeAQKbAQUWAgMBAAQL\n" +
            "CQgHBRUKCQgLApkBAAB7pgD/aw7jzdWLYWCzh6O/wXKTI73ohWsyhiHDc53gvJYT\n" +
            "IN8BAOLXkvEkmbpPaxd0Q1WKB3AMmIQvGM61uaK+4m63GRYIuDgEY2veRhIKKwYB\n" +
            "BAGXVQEFAQEHQJLNePJIeqWJzEbdCWtV5MirAqK0vWxazHQwIOZPULx0AwEIB4h1\n" +
            "BBgWCgAdBQJja95GAp4BApsMBRYCAwEABAsJCAcFFQoJCAsACgkQLA08Ejz1HAjk\n" +
            "+AEA2XDeWv3vEK/izQSMl1y4CVhrC+fZ7ChwWw1EZ8WxJzEBAMm0vLvkbG/NsluA\n" +
            "n2gOAi0Uwt3g3OWOEMGFsy6IFjcIuDMEY2veRhYJKwYBBAHaRw8BAQdA3HcELn1i\n" +
            "6ugEpLuCVgQXfSaZUVYStHF1rkxO97lpbbuI1QQYFgoAfQUCY2veRgKeAQKbAgUW\n" +
            "AgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNr3kYACgkQ7NC/hj9lyaWVAwEA3ze1\n" +
            "LCi1reGfB5tS3Au6A8aalyk4UV0iVOXxwV5r+E4BAJGzZMFF/iQ/rOcSAsHPp4gg\n" +
            "ezZALDIkT2Hrn6iLDdsLAAoJECwNPBI89RwIuBIBAMxGu/s4maOFozcO4JoCZTsL\n" +
            "HGy70SG6UuVQjK0EyJJ1APoDEfK+qTlC7/FoijMA6Ew9aesZ2IHgpwA7jlyHSgwL\n" +
            "Dw==\n" +
            "=c1PZ\n" +
            "-----END PGP PUBLIC KEY BLOCK-----";

    @Test
    @FailOnSystemExit
    public void encryptAndDecryptAMessage() throws IOException {
        // Juliets key and cert
        File julietKeyFile = pipeStdoutToFile("juliet.key");
        assertSuccess(executeCommand("generate-key", "Juliet <juliet@capulet.lit>"));

        pipeFileToStdin(julietKeyFile);
        File julietCertFile = pipeStdoutToFile("juliet.cert");
        assertSuccess(executeCommand("extract-cert"));

        // Romeos key and cert
        File romeoKeyFile = pipeStdoutToFile("romeo.key");
        assertSuccess(executeCommand("generate-key", "Romeo <romeo@montague.lit>"));

        File romeoCertFile = pipeStdoutToFile("romeo.cert");
        pipeFileToStdin(romeoKeyFile);
        assertSuccess(executeCommand("extract-cert"));

        // Romeo encrypts signs and encrypts for Juliet and himself
        String msg = "Hello World!\n";
        File encryptedMessageFile = pipeStdoutToFile("msg.asc");
        pipeStringToStdin(msg);
        assertSuccess(executeCommand("encrypt", "--sign-with", romeoKeyFile.getAbsolutePath(),
                julietCertFile.getAbsolutePath(), romeoCertFile.getAbsolutePath()));

        // Juliet can decrypt and verify with Romeos cert
        pipeFileToStdin(encryptedMessageFile);
        File verificationsFile = nonExistentFile("verifications");
        ByteArrayOutputStream decrypted = pipeStdoutToStream();
        assertSuccess(executeCommand("decrypt", "--verifications-out", verificationsFile.getAbsolutePath(),
                "--verify-with", romeoCertFile.getAbsolutePath(),
                julietKeyFile.getAbsolutePath()));
        assertEquals(msg, decrypted.toString());

        // Romeo can decrypt and verify too
        pipeFileToStdin(encryptedMessageFile);
        File anotherVerificationsFile = nonExistentFile("anotherVerifications");
        decrypted = pipeStdoutToStream();
        assertSuccess(executeCommand("decrypt", "--verifications-out", anotherVerificationsFile.getAbsolutePath(),
                "--verify-with", romeoCertFile.getAbsolutePath(),
                romeoKeyFile.getAbsolutePath()));
        assertEquals(msg, decrypted.toString());

        String julietsVerif = readStringFromFile(verificationsFile);
        String romeosVerif = readStringFromFile(anotherVerificationsFile);
        assertEquals(julietsVerif, romeosVerif);
        assertFalse(julietsVerif.isEmpty());
        assertEquals(115, julietsVerif.length()); // 115 is number of symbols in [DATE, FINGER, FINGER, MODE] for V4
    }

    @Test
    public void testMissingArgumentsIfNoArgsSupplied() throws IOException {
        int exit = executeCommand("encrypt");
        assertEquals(SOPGPException.MissingArg.EXIT_CODE, exit);
    }

    @Test
    @Disabled("Disabled, since we now read certificates from secret keys")
    public void testEncryptingForKeyFails() throws IOException {
        File notACert = writeFile("key.asc", KEY);

        pipeStringToStdin("Hello, World!");
        ByteArrayOutputStream out = pipeStdoutToStream();
        int exitCode = executeCommand("encrypt", notACert.getAbsolutePath());
        assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
        assertEquals(0, out.size());
    }

    @Test
    public void testEncrypt_SignWithCertFails() throws IOException {
        File cert = writeFile("cert.asc", CERT);
        // noinspection UnnecessaryLocalVariable
        File notAKey = cert;

        pipeStringToStdin("Hello, World!");
        ByteArrayOutputStream out = pipeStdoutToStream();
        int exitCode = executeCommand("encrypt", "--sign-with", notAKey.getAbsolutePath(), cert.getAbsolutePath());
        assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
        assertEquals(0, out.size());
    }

    @Test
    public void testDecryptVerifyOut_withoutVerifyWithFails() throws IOException {
        File key = writeFile("key.asc", KEY);

        File verifications = nonExistentFile("verifications");
        ByteArrayOutputStream out = pipeStdoutToStream();
        int exitCode = executeCommand("decrypt", "--verifications-out",
                verifications.getAbsolutePath(), key.getAbsolutePath());

        assertEquals(SOPGPException.IncompleteVerification.EXIT_CODE, exitCode);
        assertEquals(0, out.size());
    }

    @Test
    public void testVerificationsOutAlreadyExistFails() throws IOException {
        File key = writeFile("key.asc", KEY);
        File cert = writeFile("cert.asc", CERT);

        File verifications = writeFile("verifications", "this file is not empty");
        ByteArrayOutputStream out = pipeStdoutToStream();
        int exitCode = executeCommand("decrypt", "--verify-with", cert.getAbsolutePath(),
                "--verifications-out", verifications.getAbsolutePath(),
                key.getAbsolutePath());

        assertEquals(SOPGPException.OutputExists.EXIT_CODE, exitCode);
        assertEquals(0, out.size());
    }

    @Test
    public void testSessionKeyOutWritesSessionKeyOut() throws IOException {
        File key = writeFile("key.asc", KEY);
        File sessionKeyFile = nonExistentFile("session.key");

        String plaintext = "Hello, World!\n";
        String ciphertext = "-----BEGIN PGP MESSAGE-----\n" +
                "Version: PGPainless\n" +
                "\n" +
                "hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" +
                "48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" +
                "0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" +
                "/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" +
                "K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" +
                "LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" +
                "FpVU\n" +
                "=edS5\n" +
                "-----END PGP MESSAGE-----";
        String sessionKey = "9:B6FAD96B7ED2DA27D8A36EAEA75DAB7AC587180B14D8A24BD7263524F3DDECC3\n";

        pipeStringToStdin(ciphertext);
        ByteArrayOutputStream plaintextOut = pipeStdoutToStream();
        assertSuccess(executeCommand("decrypt", "--session-key-out",
                sessionKeyFile.getAbsolutePath(), key.getAbsolutePath()));

        assertEquals(plaintext, plaintextOut.toString());
        String resultSessionKey = readStringFromFile(sessionKeyFile);
        assertEquals(sessionKey, resultSessionKey);
    }

    @Test
    public void decryptMessageWithSessionKey() throws IOException {
        String plaintext = "Hello, World!\n";
        String ciphertext = "-----BEGIN PGP MESSAGE-----\n" +
                "Version: PGPainless\n" +
                "\n" +
                "hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" +
                "48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" +
                "0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" +
                "/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" +
                "K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" +
                "LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" +
                "FpVU\n" +
                "=edS5\n" +
                "-----END PGP MESSAGE-----";
        String sessionKey = "9:B6FAD96B7ED2DA27D8A36EAEA75DAB7AC587180B14D8A24BD7263524F3DDECC3\n";

        File sessionKeyFile = writeFile("session.key", sessionKey);

        pipeStringToStdin(ciphertext);
        ByteArrayOutputStream plaintextOut = pipeStdoutToStream();
        assertSuccess(executeCommand("decrypt", "--with-session-key", sessionKeyFile.getAbsolutePath()));

        assertEquals(plaintext, plaintextOut.toString());
    }

    @Test
    public void testDecryptWithSessionKeyVerifyWithYieldsExpectedVerifications() throws IOException {
        String plaintext = "Hello, World!\n";
        String ciphertext = "-----BEGIN PGP MESSAGE-----\n" +
                "Version: PGPainless\n" +
                "\n" +
                "hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" +
                "48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" +
                "0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" +
                "/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" +
                "K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" +
                "LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" +
                "FpVU\n" +
                "=edS5\n" +
                "-----END PGP MESSAGE-----";
        String sessionKey = "9:B6FAD96B7ED2DA27D8A36EAEA75DAB7AC587180B14D8A24BD7263524F3DDECC3\n";

        File cert = writeFile("cert.asc", CERT);
        File sessionKeyFile = writeFile("session.key", sessionKey);
        File verifications = nonExistentFile("verifications");

        pipeStringToStdin(ciphertext);
        ByteArrayOutputStream out = pipeStdoutToStream();
        assertSuccess(executeCommand("decrypt", "--with-session-key", sessionKeyFile.getAbsolutePath(),
                "--verify-with", cert.getAbsolutePath(), "--verifications-out", verifications.getAbsolutePath()));

        assertEquals(plaintext, out.toString());
        String verificationString = readStringFromFile(verifications);
        assertEquals("2022-11-09T17:22:48Z C0DCEC44B1A173664B05DABCECD0BF863F65C9A5 A2EC077FC977E15DD799EFF92C0D3C123CF51C08 mode:binary\n",
                verificationString);
    }

    @Test
    public void encryptAndDecryptMessageWithPassphrase() throws IOException {
        File passwordFile = writeFile("password", "c1tiz€n4");
        String message = "I cannot think of meaningful messages for test vectors rn";

        pipeStringToStdin(message);
        ByteArrayOutputStream ciphertext = pipeStdoutToStream();
        assertSuccess(executeCommand("encrypt", "--with-password", passwordFile.getAbsolutePath()));

        String ciphertextString = ciphertext.toString();
        assertTrue(ciphertextString.startsWith("-----BEGIN PGP MESSAGE-----\n"));

        pipeBytesToStdin(ciphertext.toByteArray());
        ByteArrayOutputStream plaintext = pipeStdoutToStream();
        assertSuccess(executeCommand("decrypt", "--with-password", passwordFile.getAbsolutePath()));

        assertEquals(message, plaintext.toString());
    }

    @Test
    public void testEncryptWithIncapableCert() throws PGPException,
            InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
        PGPSecretKeyRing secretKeys = PGPainless.buildKeyRing()
                .addUserId("No Crypt <no@crypt.key>")
                .setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519),
                        KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA))
                .build();
        PGPPublicKeyRing cert = PGPainless.extractCertificate(secretKeys);
        File certFile = writeFile("cert.pgp", cert.getEncoded());

        pipeStringToStdin("Hello, World!\n");
        ByteArrayOutputStream out = pipeStdoutToStream();
        int exitCode = executeCommand("encrypt", certFile.getAbsolutePath());

        assertEquals(SOPGPException.CertCannotEncrypt.EXIT_CODE, exitCode);
        assertEquals(0, out.size());
    }

    @Test
    public void testSignWithIncapableKey()
            throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
        PGPSecretKeyRing secretKeys = PGPainless.buildKeyRing()
                .addUserId("Cannot Sign <cannot@sign.key>")
                .setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER))
                .addSubkey(KeySpec.getBuilder(
                        KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
                .build();
        File keyFile = writeFile("key.pgp", secretKeys.getEncoded());
        File certFile = writeFile("cert.pgp", PGPainless.extractCertificate(secretKeys).getEncoded());

        pipeStringToStdin("Hello, World!\n");
        ByteArrayOutputStream out = pipeStdoutToStream();
        int exitCode = executeCommand("encrypt", "--sign-with", keyFile.getAbsolutePath(),
                certFile.getAbsolutePath());

        assertEquals(SOPGPException.KeyCannotSign.EXIT_CODE, exitCode);
        assertEquals(0, out.size());
    }

    @Test
    public void testEncryptDecryptRoundTripWithPasswordProtectedKey() throws IOException {
        // generate password protected key
        File passwordFile = writeFile("password", "fooBarBaz420");
        File keyFile = pipeStdoutToFile("key.asc");
        assertSuccess(executeCommand("generate-key",
                "--with-key-password", passwordFile.getAbsolutePath(),
                "Pascal Password <pascal@password.protected>"));

        // extract cert
        File certFile = pipeStdoutToFile("cert.asc");
        pipeFileToStdin(keyFile);
        assertSuccess(executeCommand("extract-cert"));

        // encrypt and sign message
        String msg = "Hello, World!\n";
        pipeStringToStdin(msg);
        File encryptedFile = pipeStdoutToFile("msg.asc");
        assertSuccess(executeCommand("encrypt",
                "--sign-with", keyFile.getAbsolutePath(),
                "--with-key-password", passwordFile.getAbsolutePath(),
                "--no-armor",
                "--as", "binary",
                certFile.getAbsolutePath()));

        // Decrypt
        File verificationsFile = nonExistentFile("verifications");
        pipeFileToStdin(encryptedFile);
        ByteArrayOutputStream out = pipeStdoutToStream();
        assertSuccess(executeCommand("decrypt",
                "--verify-with", certFile.getAbsolutePath(),
                "--verifications-out", verificationsFile.getAbsolutePath(),
                "--with-key-password", passwordFile.getAbsolutePath(),
                keyFile.getAbsolutePath()));

        assertEquals(msg, out.toString());
    }

    @Test
    public void decryptGarbageFails() throws IOException {
        File keyFile = writeFile("key.asc", KEY);
        pipeStringToStdin("Some Garbage!");
        int exitCode = executeCommand("decrypt", keyFile.getAbsolutePath());
        assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
    }

    @Test
    public void decryptMessageWithWrongKeyFails() throws IOException {
        File keyFile = pipeStdoutToFile("key.asc");
        assertSuccess(executeCommand("generate-key", "Bob <bob@pgpainless.org>"));
        // message was *not* created with key above
        String ciphertext = "-----BEGIN PGP MESSAGE-----\n" +
                "Version: PGPainless\n" +
                "\n" +
                "hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" +
                "48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" +
                "0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" +
                "/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" +
                "K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" +
                "LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" +
                "FpVU\n" +
                "=edS5\n" +
                "-----END PGP MESSAGE-----";

        pipeStringToStdin(ciphertext);
        ByteArrayOutputStream out = pipeStdoutToStream();
        int exitCode = executeCommand("decrypt", keyFile.getAbsolutePath());
        assertEquals(SOPGPException.CannotDecrypt.EXIT_CODE, exitCode);
        assertEquals(0, out.size());
    }

    @Test
    public void encryptWithPasswordADecryptWithPasswordBFails() throws IOException {
        File password1 = writeFile("password1", "swordfish");
        File password2 = writeFile("password2", "orange");

        pipeStringToStdin("Bonjour, le monde!\n");
        ByteArrayOutputStream ciphertextOut = pipeStdoutToStream();
        assertSuccess(executeCommand("encrypt", "--with-password", password1.getAbsolutePath()));

        pipeBytesToStdin(ciphertextOut.toByteArray());
        ByteArrayOutputStream out = pipeStdoutToStream();
        int exitCode = executeCommand("decrypt", "--with-password", password2.getAbsolutePath());
        assertEquals(SOPGPException.CannotDecrypt.EXIT_CODE, exitCode);
        assertEquals(0, out.size());
    }

    @Test
    public void encryptWithGarbageCertFails() throws IOException {
        File garbageCert = writeFile("cert.asc", "This is garbage!");

        pipeStringToStdin("Hallo, Welt!\n");
        ByteArrayOutputStream out = pipeStdoutToStream();
        int exitCode = executeCommand("encrypt", garbageCert.getAbsolutePath());

        assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
        assertEquals(0, out.size());
    }

    @Test
    public void encrypt_signWithGarbageKeyFails() throws IOException {
        File cert = writeFile("cert.asc", CERT);
        File garbageKey = writeFile("key.asc", "This is not a key!");

        pipeStringToStdin("Salut!\n");
        ByteArrayOutputStream out = pipeStdoutToStream();
        int exitCode = executeCommand("encrypt", "--sign-with", garbageKey.getAbsolutePath(),
                cert.getAbsolutePath());

        assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
        assertEquals(0, out.size());
    }

    @Test
    public void decrypt_withGarbageKeyFails() throws IOException {
        File key = writeFile("key.asc", "this is garbage");
        String ciphertext = "-----BEGIN PGP MESSAGE-----\n" +
                "Version: PGPainless\n" +
                "\n" +
                "hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" +
                "48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" +
                "0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" +
                "/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" +
                "K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" +
                "LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" +
                "FpVU\n" +
                "=edS5\n" +
                "-----END PGP MESSAGE-----";

        pipeStringToStdin(ciphertext);
        ByteArrayOutputStream out = pipeStdoutToStream();
        int exitCode = executeCommand("decrypt", key.getAbsolutePath());

        assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
        assertEquals(0, out.size());
    }

    @Test
    public void decrypt_verifyWithGarbageCertFails() throws IOException {
        File key = writeFile("key.asc", KEY);
        File cert = writeFile("cert.asc", "now this is garbage");
        String ciphertext = "-----BEGIN PGP MESSAGE-----\n" +
                "Version: PGPainless\n" +
                "\n" +
                "hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" +
                "48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" +
                "0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" +
                "/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" +
                "K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" +
                "LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" +
                "FpVU\n" +
                "=edS5\n" +
                "-----END PGP MESSAGE-----";
        File verificationsFile = nonExistentFile("verifications");

        pipeStringToStdin(ciphertext);
        ByteArrayOutputStream out = pipeStdoutToStream();
        int exitCode = executeCommand("decrypt", key.getAbsolutePath(),
                "--verify-with", cert.getAbsolutePath(),
                "--verifications-out", verificationsFile.getAbsolutePath());

        assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
        assertEquals(0, out.size());
    }

    @Test
    public void encryptWithProtectedKey_wrongPassphraseFails() throws IOException {
        File password = writeFile("passphrase1", "orange");
        File wrongPassword = writeFile("passphrase2", "blue");

        File keyFile = pipeStdoutToFile("key.asc");
        assertSuccess(executeCommand("generate-key", "Pedro <pedro@pgpainless.org>",
                "--with-key-password", password.getAbsolutePath()));

        File certFile = pipeStdoutToFile("cert.asc");
        pipeFileToStdin(keyFile);
        assertSuccess(executeCommand("extract-cert"));

        // Use no passphrase to unlock the key
        String msg = "Guten Tag, Welt!\n";
        pipeStringToStdin(msg);
        ByteArrayOutputStream out = pipeStdoutToStream();
        int exitCode = executeCommand("encrypt", "--sign-with", keyFile.getAbsolutePath(),
                certFile.getAbsolutePath());
        assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode);
        assertEquals(0, out.size());

        // use wrong passphrase to unlock key when signing message
        pipeStringToStdin("Guten Tag, Welt!\n");
        out = pipeStdoutToStream();
        exitCode = executeCommand("encrypt", "--sign-with", keyFile.getAbsolutePath(),
                "--with-key-password", wrongPassword.getAbsolutePath(),
                certFile.getAbsolutePath());
        assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode);
        assertEquals(0, out.size());

        // use correct passphrase and encrypt+sign message
        pipeStringToStdin("Guten Tag, Welt!\n");
        out = pipeStdoutToStream();
        assertSuccess(executeCommand("encrypt", "--sign-with", keyFile.getAbsolutePath(),
                "--with-key-password", password.getAbsolutePath(),
                certFile.getAbsolutePath()));
        String ciphertext = out.toString();

        // Use no passphrase to decrypt key when decrypting
        pipeStringToStdin(ciphertext);
        out = pipeStdoutToStream();
        exitCode = executeCommand("decrypt", keyFile.getAbsolutePath());
        assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode);
        assertEquals(0, out.size());

        // Use wrong passphrase to decrypt key when decrypting
        pipeStringToStdin(ciphertext);
        out = pipeStdoutToStream();
        exitCode = executeCommand("decrypt", "--with-key-password", wrongPassword.getAbsolutePath(),
                keyFile.getAbsolutePath());
        assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode);
        assertEquals(0, out.size());

        // User correct passphrase to decrypt key when decrypting
        pipeStringToStdin(ciphertext);
        out = pipeStdoutToStream();
        assertSuccess(executeCommand("decrypt", "--with-key-password", password.getAbsolutePath(),
                keyFile.getAbsolutePath()));
        assertEquals(msg, out.toString());
    }

    @Test
    public void decryptMalformedMessageYieldsBadData() throws IOException {
        // Message contains encrypted data packet which contains the plaintext directly - no literal data packet.
        // It is therefore malformed.
        String malformed = "-----BEGIN PGP MESSAGE-----\n" +
                "Version: BCPG v1.72b04\n" +
                "\n" +
                "hF4D831k4umlLu4SAQdApKA6VDKSLQvwS2kbWqlhcXD8XHdFkSccqv5tBptZnBgw\n" +
                "nZNXVhwUpap0ymb4jPTD+EVPKOfPyy04ouIGZAJKkfYDeSL/8sKcbnPPuQJYYjGQ\n" +
                "ySDNmidrtTonwcSuwAfRyn74BBqOVhrr8GXkVIfevIlZFQ==\n" +
                "=wIgl\n" +
                "-----END PGP MESSAGE-----";
        File key = writeFile("key.asc", KEY);
        pipeStringToStdin(malformed);
        int exitCode = executeCommand("decrypt", key.getAbsolutePath());
        assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
    }

    @Test
    public void decryptWithPasswordWithPendingWhitespaceWorks() throws IOException {
        assertEncryptWithPasswordADecryptWithPasswordBWorks("sw0rdf1sh", "sw0rdf1sh \n");
    }

    @Test
    public void encryptWithTrailingWhitespaceDecryptWithoutWorks() throws IOException {
        assertEncryptWithPasswordADecryptWithPasswordBWorks("sw0rdf1sh \n", "sw0rdf1sh");
    }

    @Test
    public void decryptWithWhitespacePasswordWorks() throws IOException {
        // is encrypted for "sw0rdf1sh \n"
        String encryptedToPasswordWithTrailingWhitespace = "-----BEGIN PGP MESSAGE-----\n" +
                "\n" +
                "jA0ECQMC32tEJug0BCpg0kABfT3dKgA4K8XGpk2ul67BXLZD//fCCSmIQIWnNhE1\n" +
                "6q97xFQ628K8f/58+XoBzLqLDT+LEz9Bz+Yg9QfzkEFy\n" +
                "=2Y+K\n" +
                "-----END PGP MESSAGE-----";
        pipeStringToStdin(encryptedToPasswordWithTrailingWhitespace);
        File password = writeFile("password", "sw0rdf1sh \n");
        ByteArrayOutputStream plaintext = pipeStdoutToStream();
        assertSuccess(executeCommand("decrypt", "--with-password", password.getAbsolutePath()));

        assertEquals("Hello, World!\n", plaintext.toString());
    }

    private void assertEncryptWithPasswordADecryptWithPasswordBWorks(String passwordA, String passwordB)
            throws IOException {
        File passwordAFile = writeFile("password", passwordA);
        File passwordBFile = writeFile("passwordWithWS", passwordB);

        String msg = "Hello, World!\n";
        pipeStringToStdin(msg);
        ByteArrayOutputStream ciphertext = pipeStdoutToStream();
        assertSuccess(executeCommand("encrypt", "--with-password", passwordAFile.getAbsolutePath()));

        pipeStringToStdin(ciphertext.toString());
        ByteArrayOutputStream plaintext = pipeStdoutToStream();
        assertSuccess(executeCommand("decrypt", "--with-password", passwordBFile.getAbsolutePath()));

        assertEquals(msg, plaintext.toString());
    }

    @Test
    public void testDecryptWithoutDecryptionOptionFails() throws IOException {
        String ciphertext = "-----BEGIN PGP MESSAGE-----\n" +
                "Version: PGPainless\n" +
                "\n" +
                "hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" +
                "48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" +
                "0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" +
                "/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" +
                "K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" +
                "LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" +
                "FpVU\n" +
                "=edS5\n" +
                "-----END PGP MESSAGE-----";
        pipeStringToStdin(ciphertext);
        int exitCode = executeCommand("decrypt");
        assertEquals(SOPGPException.MissingArg.EXIT_CODE, exitCode);
    }

    @Test
    public void testEncryptDecryptWithFreshRSAKey() throws IOException {
        // Generate key
        File passwordFile = writeFile("password", "sw0rdf1sh");
        File keyFile = pipeStdoutToFile("key.asc");
        assertSuccess(executeCommand("generate-key", "--profile=rfc4880", "--with-key-password", passwordFile.getAbsolutePath(), "Alice <alice@example.org>"));

        File certFile = pipeStdoutToFile("cert.asc");
        pipeFileToStdin(keyFile);
        assertSuccess(executeCommand("extract-cert"));

        // Write plaintext
        File plaintextFile = writeFile("msg.txt", "Hello, World!\n");

        // Encrypt
        File ciphertextFile = pipeStdoutToFile("msg.asc");
        pipeFileToStdin(plaintextFile);
        assertSuccess(executeCommand("encrypt", "--profile=rfc4880", certFile.getAbsolutePath()));

        ByteArrayOutputStream decrypted = pipeStdoutToStream();
        pipeFileToStdin(ciphertextFile);
        assertSuccess(executeCommand("decrypt", "--with-key-password", passwordFile.getAbsolutePath(), keyFile.getAbsolutePath()));

        assertEquals("Hello, World!\n", decrypted.toString());
    }
}
