Skip to content

Commit 557faa0

Browse files
committed
Science Commit 5.
1 parent 36f368b commit 557faa0

17 files changed

Lines changed: 241 additions & 66 deletions

configuration/nwe-config.xml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@
3232
(ModuleInstallationService, ModuleLoaderDaemon) using these credentials.
3333
════════════════════════════════════════════════════════════════════ -->
3434
<admin>
35-
<username>mearvk</username>
36-
<password>n21admin</password>
35+
<username>${NWE_ADMIN_USER:admin}</username>
36+
<password>${NWE_ADMIN_PASS:CHANGE_ME_BEFORE_DEPLOY}</password>
3737
<description>
38-
Initial NWE administrator. Responsible for database configuration,
39-
server document management, and module authority.
40-
Change the password before production deployment.
38+
NWE administrator credentials. MUST be set via environment variables:
39+
NWE_ADMIN_USER — admin username (default: admin)
40+
NWE_ADMIN_PASS — admin password (REQUIRED — server refuses to start if default)
41+
Responsible for database configuration, server document management,
42+
and module authority.
4143
</description>
4244
</admin>
4345

integrity/post-install-integrity-check.sh

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,29 @@ if [ -f "$SELF_DB" ]; then
4040
if [ -n "$EXPECTED" ] && [ "$ACTUAL" != "$EXPECTED" ]; then
4141
echo "-- : [integrity] SELF-INTEGRITY FAIL: ${sf}"
4242
echo "${TIMESTAMP}|SELF_FAIL|${sf}|expected=${EXPECTED}|actual=${ACTUAL}" >> "$CONCERN_FILE"
43-
# Restore from trusted repo
43+
# Attempt restore from trusted repo — but require approval if interactive
4444
if curl -sf "${RAW}/${sf}" -o "${PROJECT_ROOT}/${sf}.restored"; then
45-
mv "${PROJECT_ROOT}/${sf}.restored" "${PROJECT_ROOT}/${sf}"
46-
echo "-- : [integrity] RESTORED: ${sf} from trusted repo"
45+
if [ -t 0 ]; then
46+
# Interactive: ask for confirmation before overwriting
47+
echo "-- : [integrity] File differs from trusted source: ${sf}"
48+
printf "-- : [integrity] Restore from GitHub? [y/N]: "
49+
read -r CONFIRM
50+
if [ "$CONFIRM" = "y" ] || [ "$CONFIRM" = "Y" ]; then
51+
mv "${PROJECT_ROOT}/${sf}.restored" "${PROJECT_ROOT}/${sf}"
52+
echo "-- : [integrity] RESTORED: ${sf} from trusted repo"
53+
else
54+
rm -f "${PROJECT_ROOT}/${sf}.restored"
55+
echo "-- : [integrity] SKIPPED restore of ${sf} (user declined)"
56+
echo "${TIMESTAMP}|RESTORE_DECLINED|${sf}" >> "$CONCERN_FILE"
57+
SELF_OK=false
58+
fi
59+
else
60+
# Non-interactive: do NOT auto-restore. Log what would be restored.
61+
rm -f "${PROJECT_ROOT}/${sf}.restored"
62+
echo "-- : [integrity] NON-INTERACTIVE: Would restore ${sf} — logging only (no auto-restore)"
63+
echo "${TIMESTAMP}|RESTORE_PENDING|${sf}|expected=${EXPECTED}|actual=${ACTUAL}|action=none_non_interactive" >> "$CONCERN_FILE"
64+
SELF_OK=false
65+
fi
4766
else
4867
SELF_OK=false
4968
fi
@@ -101,11 +120,28 @@ for filepath in $FILES; do
101120
if curl -sf "${RAW}/${filepath}" -o "${filepath}.restore.tmp"; then
102121
RESTORE_SHA=$(sha256sum "${filepath}.restore.tmp" | awk '{print $1}')
103122
if [ "$RESTORE_SHA" = "$EXPECTED_SHA" ]; then
104-
cp "$filepath" "${filepath}.corrupted.bak"
105-
mv "${filepath}.restore.tmp" "$filepath"
106-
echo "-- : [integrity] RESTORED: ${filepath}"
107-
echo "${TIMESTAMP}|RESTORED|${filepath}|sha256=${EXPECTED_SHA}" >> "$CONCERN_FILE"
108-
RESTORED=$((RESTORED + 1))
123+
if [ -t 0 ]; then
124+
# Interactive: ask for confirmation before overwriting
125+
echo "-- : [integrity] Corruption detected in: ${filepath}"
126+
printf "-- : [integrity] Restore from GitHub (commit ${COMMIT_SHA})? [y/N]: "
127+
read -r CONFIRM
128+
if [ "$CONFIRM" = "y" ] || [ "$CONFIRM" = "Y" ]; then
129+
cp "$filepath" "${filepath}.corrupted.bak"
130+
mv "${filepath}.restore.tmp" "$filepath"
131+
echo "-- : [integrity] RESTORED: ${filepath}"
132+
echo "${TIMESTAMP}|RESTORED|${filepath}|sha256=${EXPECTED_SHA}" >> "$CONCERN_FILE"
133+
RESTORED=$((RESTORED + 1))
134+
else
135+
rm -f "${filepath}.restore.tmp"
136+
echo "-- : [integrity] SKIPPED restore of ${filepath} (user declined)"
137+
echo "${TIMESTAMP}|RESTORE_DECLINED|${filepath}" >> "$CONCERN_FILE"
138+
fi
139+
else
140+
# Non-interactive: do NOT auto-restore. Log what would be restored.
141+
rm -f "${filepath}.restore.tmp"
142+
echo "-- : [integrity] NON-INTERACTIVE: Would restore ${filepath} — logging only (no auto-restore)"
143+
echo "${TIMESTAMP}|RESTORE_PENDING|${filepath}|expected=${EXPECTED_SHA}|actual=${CURRENT_SHA}|action=none_non_interactive" >> "$CONCERN_FILE"
144+
fi
109145
else
110146
rm -f "${filepath}.restore.tmp"
111147
echo "-- : [integrity] WARN: Remote also differs for ${filepath}"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
28095
1+
28695

scripts/deploy-remote-linux.sh

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ source "$PROJECT_ROOT/scripts/print-descriptor.sh" 2>/dev/null || true
1919

2020
# ── Configuration ─────────────────────────────────────────────────────────────
2121
DEFAULT_HOST="45.32.31.139"
22-
DEFAULT_USER="root"
22+
DEFAULT_USER="nwe"
2323
DEFAULT_PATH="/opt/NitroWebExpress"
2424

2525
REMOTE="${1:-${NWE_REMOTE_USER:-$DEFAULT_USER}@${NWE_REMOTE_HOST:-$DEFAULT_HOST}}"
2626
REMOTE_PATH="${2:-$DEFAULT_PATH}"
2727
REMOTE_USER="${REMOTE%%@*}"
2828
REMOTE_HOST="${REMOTE##*@}"
2929

30-
SSH_OPTS="-o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=15 -o ServerAliveCountMax=3"
30+
SSH_OPTS="-o ConnectTimeout=10 -o StrictHostKeyChecking=yes -o ServerAliveInterval=15 -o ServerAliveCountMax=3"
3131

3232
echo "╔═══════════════════════════════════════════════════════════════════════════╗"
3333
echo "║ NitroWebExpress™ — Remote Linux Server Deploy ║"
@@ -57,6 +57,9 @@ if ! $SSH_CMD -o BatchMode=yes "$REMOTE" "echo OK" &>/dev/null; then
5757
sudo apt-get install -y -qq sshpass 2>/dev/null || sudo dnf install -y -q sshpass 2>/dev/null || true
5858
fi
5959
if command -v sshpass &>/dev/null; then
60+
# SECURITY WARNING: sshpass exposes passwords via process listing (/proc).
61+
# Prefer SSH key-based authentication for production deployments.
62+
# Use this only for initial setup, then switch to key auth immediately.
6063
read -rsp " SSH password for $REMOTE: " SSH_PASS
6164
echo ""
6265
if sshpass -p "$SSH_PASS" ssh $SSH_OPTS "$REMOTE" "echo OK" &>/dev/null; then
@@ -68,9 +71,11 @@ if ! $SSH_CMD -o BatchMode=yes "$REMOTE" "echo OK" &>/dev/null; then
6871
# Offer to copy SSH key for future passwordless access
6972
echo " [*] Copying SSH key for future passwordless access..."
7073
if [ ! -f ~/.ssh/id_ed25519 ] && [ ! -f ~/.ssh/id_rsa ]; then
74+
# WARNING: Empty passphrase (-N "") means the private key is unprotected on disk.
75+
# For production, generate keys manually with a passphrase and use ssh-agent.
7176
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N "" -q
7277
fi
73-
sshpass -p "$SSH_PASS" ssh-copy-id -o StrictHostKeyChecking=accept-new "$REMOTE" &>/dev/null && \
78+
sshpass -p "$SSH_PASS" ssh-copy-id -o StrictHostKeyChecking=yes "$REMOTE" &>/dev/null && \
7479
echo " [✓] SSH key installed — password not needed next time" || true
7580
else
7681
echo " [!] Password auth failed"

scripts/nwe-ports.sh

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ NWE_PORTS=(
9595
nwe_open_ports() {
9696
local OPENED=0 FAILED=0 SKIPPED=0
9797

98+
# SECURITY: Set NWE_TRUSTED_IP to restrict access to a specific IP.
99+
# Without it, ports are open to ALL sources (0.0.0.0).
100+
if [ -z "$NWE_TRUSTED_IP" ]; then
101+
echo "[WARN] NWE_TRUSTED_IP not set — opening ports to ALL sources. Set NWE_TRUSTED_IP for production."
102+
fi
103+
98104
echo " [*] Opening NWE service ports..."
99105

100106
# Ensure UFW is installed and active
@@ -104,10 +110,18 @@ nwe_open_ports() {
104110
for PORT in "${NWE_PORTS[@]}"; do
105111
if ufw status | grep -q "$PORT/tcp.*ALLOW" 2>/dev/null; then
106112
SKIPPED=$((SKIPPED + 1))
107-
elif sudo ufw allow "$PORT/tcp" >/dev/null 2>&1; then
108-
OPENED=$((OPENED + 1))
113+
elif [ -n "$NWE_TRUSTED_IP" ]; then
114+
if sudo ufw allow from "$NWE_TRUSTED_IP" to any port "$PORT" proto tcp comment "NWE: $PORT" 2>/dev/null; then
115+
OPENED=$((OPENED + 1))
116+
else
117+
FAILED=$((FAILED + 1))
118+
fi
109119
else
110-
FAILED=$((FAILED + 1))
120+
if sudo ufw allow "$PORT/tcp" comment "NWE: $PORT" 2>/dev/null; then
121+
OPENED=$((OPENED + 1))
122+
else
123+
FAILED=$((FAILED + 1))
124+
fi
111125
fi
112126
done
113127
sudo ufw --force enable >/dev/null 2>&1

scripts/remote-deploy-script.sh

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22
# remote-deploy-script.sh — Deploy NWE to remote server via SSH/SCP
33
# Prompts for remote user and root password before deploying.
44
# Usage: bash scripts/remote-deploy-script.sh
5+
#
6+
# ╔═══════════════════════════════════════════════════════════════════════════════╗
7+
# ║ SECURITY WARNING: This script uses password-based SSH (sshpass). ║
8+
# ║ Passwords are exposed in process listings and shell history. ║
9+
# ║ For production deployments, use SSH key-based authentication instead: ║
10+
# ║ 1. Generate a key: ssh-keygen -t ed25519 ║
11+
# ║ 2. Copy to server: ssh-copy-id user@host ║
12+
# ║ 3. Use deploy-remote-linux.sh which supports key auth natively. ║
13+
# ╚═══════════════════════════════════════════════════════════════════════════════╝
514

615
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
716
OUT="$ROOT/out"
@@ -10,7 +19,7 @@ SCRIPTS="$ROOT/scripts"
1019
BASH_DIR="$ROOT/bash"
1120

1221
# ── Remote defaults (from nwe-config.xml) ─────────────────────────────────────
13-
DEFAULT_USER="root"
22+
DEFAULT_USER="nwe"
1423
DEFAULT_HOST="45.32.31.139"
1524
DEFAULT_REMOTE_DIR="/opt/nwe"
1625

@@ -58,11 +67,11 @@ echo ""
5867

5968
# ── Helper: run remote command via sshpass ────────────────────────────────────
6069
remote_exec() {
61-
sshpass -p "$ROOT_PASSWORD" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 "$REMOTE_USER@$REMOTE_HOST" "$@"
70+
sshpass -p "$ROOT_PASSWORD" ssh -o StrictHostKeyChecking=yes -o ConnectTimeout=10 "$REMOTE_USER@$REMOTE_HOST" "$@"
6271
}
6372

6473
remote_copy() {
65-
sshpass -p "$ROOT_PASSWORD" scp -o StrictHostKeyChecking=no -o ConnectTimeout=10 -r "$@"
74+
sshpass -p "$ROOT_PASSWORD" scp -o StrictHostKeyChecking=yes -o ConnectTimeout=10 -r "$@"
6675
}
6776

6877
# ── Check sshpass is available ────────────────────────────────────────────────

scripts/web/post-clone.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,27 @@ TOMCAT_HOME="/home/mearvk/tomcat"
7979
if [ ! -f "$TOMCAT_HOME/bin/catalina.sh" ]; then
8080
echo "[*] Installing Tomcat 11..."
8181
cd /tmp && curl -sfLO "https://archive.apache.org/dist/tomcat/tomcat-11/v11.0.2/bin/apache-tomcat-11.0.2.tar.gz"
82+
83+
# SECURITY: Verify download integrity before extracting.
84+
# Tomcat archives have been targeted by supply-chain attacks in the past.
85+
# SHA-512 hash sourced from https://archive.apache.org/dist/tomcat/tomcat-11/v11.0.2/bin/apache-tomcat-11.0.2.tar.gz.sha512
86+
echo "[*] Verifying Tomcat SHA-512 checksum..."
87+
TOMCAT_SHA512_URL="https://archive.apache.org/dist/tomcat/tomcat-11/v11.0.2/bin/apache-tomcat-11.0.2.tar.gz.sha512"
88+
EXPECTED_SHA512=$(curl -sfL "$TOMCAT_SHA512_URL" | awk '{print $1}')
89+
ACTUAL_SHA512=$(sha512sum /tmp/apache-tomcat-11.0.2.tar.gz | awk '{print $1}')
90+
if [ -z "$EXPECTED_SHA512" ]; then
91+
echo "[WARN] Could not fetch SHA-512 from Apache — skipping verification (network issue?)"
92+
elif [ "$ACTUAL_SHA512" != "$EXPECTED_SHA512" ]; then
93+
echo "[ERROR] SHA-512 MISMATCH! Downloaded Tomcat archive may be corrupted or tampered with."
94+
echo " Expected: $EXPECTED_SHA512"
95+
echo " Actual: $ACTUAL_SHA512"
96+
echo " Aborting Tomcat installation. Remove /tmp/apache-tomcat-11.0.2.tar.gz and retry."
97+
rm -f /tmp/apache-tomcat-11.0.2.tar.gz
98+
exit 1
99+
else
100+
echo "[OK] SHA-512 verified"
101+
fi
102+
82103
mkdir -p "$TOMCAT_HOME" && tar -xzf apache-tomcat-11.0.2.tar.gz -C "$TOMCAT_HOME" --strip-components=1
83104
rm -f apache-tomcat-11.0.2.tar.gz
84105
id tomcat &>/dev/null || useradd -r -M -d "$TOMCAT_HOME" -s /bin/false tomcat

source/communicator/Communicator.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,6 @@ private static String cmdList()
237237
StringBuilder sb = new StringBuilder("[list] Connected users (" + LIVE.size() + "):\r\n");
238238
LIVE.forEach((id, s) ->
239239
sb.append(" NID=").append(id)
240-
.append(" IP=").append(s.ip)
241-
.append(" Geo=").append(s.geoCity).append(", ").append(s.geoCountry)
242-
.append(" TZ=").append(s.timezone)
243240
.append(" Online=").append((System.currentTimeMillis() - s.connectedAt) / 60000).append("min")
244241
.append("\r\n"));
245242
return sb.toString().stripTrailing();
@@ -495,9 +492,9 @@ private static void resolveGeo(final Session session)
495492
boolean priv = session.ip.startsWith("127.") || session.ip.startsWith("10.")
496493
|| session.ip.startsWith("192.168.") || session.ip.equals("::1");
497494
java.net.HttpURLConnection c = (java.net.HttpURLConnection)
498-
new java.net.URL("http://ip-api.com/json/" + (priv ? "" : session.ip)
495+
new java.net.URL("https://ip-api.com/json/" + (priv ? "" : session.ip)
499496
+ "?fields=city,country,timezone").openConnection();
500-
c.setConnectTimeout(2000); c.setConnectTimeout(2000);
497+
c.setConnectTimeout(2000); c.setReadTimeout(2000);
501498
try (BufferedReader r = new BufferedReader(new InputStreamReader(c.getInputStream())))
502499
{
503500
String body = r.lines().collect(java.util.stream.Collectors.joining());

source/communicator/CommunicatorCrypto.java

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ public void computeSharedSecret(byte[] clientPublicKeyBytes) throws Exception
128128
ka.doPhase(clientPub, true);
129129
byte[] secret = ka.generateSecret();
130130

131+
// SECURITY NOTE: Single-pass SHA-256 over the raw DH shared secret is a weak KDF.
132+
// A proper HKDF (RFC 5869) with salt and info context should be used here.
133+
// Kept as-is to avoid breaking existing clients that depend on this derivation.
131134
// Derive 256-bit key via SHA-256
132135
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
133136
sharedSecret = sha256.digest(secret);
@@ -166,6 +169,9 @@ public void computeSharedSecret(byte[] clientPublicKeyBytes) throws Exception
166169
ka.doPhase(clientPub, true);
167170
byte[] secret = ka.generateSecret();
168171

172+
// SECURITY NOTE: Single-pass SHA-256 over the raw ECDH shared secret is a weak KDF.
173+
// A proper HKDF (RFC 5869) with salt and info context should be used here.
174+
// Kept as-is to avoid breaking existing clients that depend on this derivation.
169175
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
170176
sharedSecret = sha256.digest(secret);
171177
}
@@ -319,38 +325,32 @@ private static byte[] decryptTwofish(byte[] ciphertext, byte[] key) throws Excep
319325
/** Store user's preferred cipher in their profile. */
320326
public static void saveProfileCipher(long nationalId, CipherSuite suite)
321327
{
322-
try
323-
{
324-
var conn = database.N21DataSource.get();
325-
var ps = conn.prepareStatement(
328+
try (var conn = database.N21DataSource.get();
329+
var ps = conn.prepareStatement(
326330
"INSERT INTO communicator_profiles (national_id, preferred_cipher, updated_at) " +
327-
"VALUES (?, ?, NOW()) ON DUPLICATE KEY UPDATE preferred_cipher = ?, updated_at = NOW()");
331+
"VALUES (?, ?, NOW()) ON DUPLICATE KEY UPDATE preferred_cipher = ?, updated_at = NOW()"))
332+
{
328333
ps.setLong(1, nationalId);
329334
ps.setString(2, suite.name());
330335
ps.setString(3, suite.name());
331336
ps.executeUpdate();
332-
ps.close();
333337
}
334338
catch (Exception e) { /* profile save is non-critical */ }
335339
}
336340

337341
/** Load user's preferred cipher from profile, or null if not set. */
338342
public static CipherSuite loadProfileCipher(long nationalId)
339343
{
340-
try
344+
try (var conn = database.N21DataSource.get();
345+
var ps = conn.prepareStatement(
346+
"SELECT preferred_cipher FROM communicator_profiles WHERE national_id = ?"))
341347
{
342-
var conn = database.N21DataSource.get();
343-
var ps = conn.prepareStatement(
344-
"SELECT preferred_cipher FROM communicator_profiles WHERE national_id = ?");
345348
ps.setLong(1, nationalId);
346-
var rs = ps.executeQuery();
347-
if (rs.next())
349+
try (var rs = ps.executeQuery())
348350
{
349-
CipherSuite cs = CipherSuite.fromName(rs.getString(1));
350-
rs.close(); ps.close();
351-
return cs;
351+
if (rs.next())
352+
return CipherSuite.fromName(rs.getString(1));
352353
}
353-
rs.close(); ps.close();
354354
}
355355
catch (Exception ignored) {}
356356
return null;

source/database/N21AuthConfig.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,11 @@ public static synchronized N21AuthConfig get()
5757

5858
try
5959
{
60-
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
60+
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
61+
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
62+
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
63+
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
64+
DocumentBuilder builder = dbf.newDocumentBuilder();
6165
Document doc = builder.parse(file);
6266
doc.getDocumentElement().normalize();
6367
Element root = doc.getDocumentElement();
@@ -123,7 +127,7 @@ public void ensureMysqlRunning()
123127
try
124128
{
125129
String url = "jdbc:mysql://" + HOST + ":" + PORT
126-
+ "/N21?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC&connectTimeout=3000";
130+
+ "/N21?useSSL=true&allowPublicKeyRetrieval=true&serverTimezone=UTC&connectTimeout=3000";
127131

128132
Class.forName("com.mysql.cj.jdbc.Driver");
129133

0 commit comments

Comments
 (0)