fixed issues with base64 length limit.

This commit is contained in:
deepend 2025-09-16 10:05:07 -06:00
parent f8f07abc2c
commit 32fd5afc65
6 changed files with 312 additions and 243 deletions

View File

@ -1,5 +1,6 @@
{
"require": {
"paragonie/sodium_compat": "^2.1"
}
}
"require": {
"paragonie/sodium_compat": "^2.1",
"ext-curl": "*"
}
}

521
index.php
View File

@ -1,237 +1,286 @@
<?php
// Load dependencies
require 'vendor/autoload.php';
use ParagonIE\Sodium\CryptoSign;
// Enable or disable debugging/logging
$debug = isset($_GET['debug']) ? (bool)$_GET['debug'] : true;
if ($debug) {
error_reporting(E_ALL);
ini_set('display_errors', 1);
} else {
error_reporting(0);
ini_set('display_errors', 0);
}
// Database setup function
function initializeDatabase($db) {
$query = "
CREATE TABLE IF NOT EXISTS storage (
pubkey_hash CHAR(64) PRIMARY KEY,
data TEXT NOT NULL,
mime_type VARCHAR(50) NOT NULL,
public_key BLOB NOT NULL
)";
$db->exec($query);
// Check if the 'public_key' column already exists; if not, add it.
$columns = $db->query("PRAGMA table_info(storage)")->fetchAll(PDO::FETCH_ASSOC);
$hasPublicKeyColumn = false;
foreach ($columns as $column) {
if ($column['name'] === 'public_key') {
$hasPublicKeyColumn = true;
break;
}
}
if (!$hasPublicKeyColumn) {
$db->exec("ALTER TABLE storage ADD COLUMN public_key BLOB NOT NULL");
}
}
// Initialize SQLite and auto-create the database and table if they don't exist
$db = new PDO('sqlite:/home/retrodig/1024clubdb/storage.db');
initializeDatabase($db);
// Helper function to respond with JSON
function respond($status, $message, $data = []) {
http_response_code($status);
echo json_encode(['message' => $message, 'data' => $data]);
exit;
}
// Enhance Base64 decoding to handle both raw and URL-safe inputs
function safeBase64Decode($input) {
// First, attempt a direct Base64 decode
$decoded = base64_decode($input, true);
if ($decoded !== false && strlen($decoded) === 32) {
return $decoded;
}
// If the direct decode fails, attempt a URL-safe decode
$urlSafeInput = str_replace(['-', '_'], ['+', '/'], $input);
return base64_decode($urlSafeInput, true);
}
// Handle Base64 encoding safely
function safeBase64Encode($input) {
return str_replace(['+', '/'], ['-', '_'], base64_encode($input));
}
// Check if 'action' is set in the query parameters before proceeding
if (isset($_GET['action'])) {
// Create or get storage space based on pubkey hash
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_GET['action'] === 'create') {
$pubkey = $_POST['pubkey'] ?? null;
if (!$pubkey) {
respond(400, "Missing public key");
}
// Decode the public key, try both raw and URL-safe Base64 decodings
$decodedPubkey = safeBase64Decode($pubkey);
if ($decodedPubkey === false || strlen($decodedPubkey) !== 32) {
respond(400, "Invalid public key");
}
$pubkey_hash = bin2hex(sodium_crypto_generichash($decodedPubkey));
$stmt = $db->prepare("SELECT * FROM storage WHERE pubkey_hash = :pubkey_hash");
$stmt->execute([':pubkey_hash' => $pubkey_hash]);
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
if ($existing) {
respond(200, "Space already exists", ['pubkey_hash' => $pubkey_hash]);
}
$stmt = $db->prepare("INSERT INTO storage (pubkey_hash, data, mime_type, public_key) VALUES (:pubkey_hash, '', 'text/plain', :public_key)");
$stmt->execute([':pubkey_hash' => $pubkey_hash, ':public_key' => $decodedPubkey]);
respond(201, "Space created", ['pubkey_hash' => $pubkey_hash]);
}
// Retrieve stored data using the public key or public key hash
if ($_SERVER['REQUEST_METHOD'] === 'GET' && $_GET['action'] === 'retrieve') {
$pubkey = $_GET['pubkey'] ?? null;
$pubkey_hash = $_GET['pubkey_hash'] ?? null;
if ($pubkey) {
// Replace spaces with '+' to account for unencoded URLs
$pubkey = str_replace(' ', '+', $pubkey);
// Attempt to decode public key (try both standard and URL-safe Base64)
$decodedPubkey = safeBase64Decode($pubkey);
if ($decodedPubkey === false || strlen($decodedPubkey) !== 32) {
respond(400, "Failed to decode public key: " . htmlspecialchars($pubkey));
}
// Hash the decoded public key
$pubkey_hash = bin2hex(sodium_crypto_generichash($decodedPubkey));
} elseif ($pubkey_hash) {
// Validate the provided public key hash (must be 64 characters long, hex format)
if (!ctype_xdigit($pubkey_hash) || strlen($pubkey_hash) !== 64) {
respond(400, "Invalid public key hash: " . htmlspecialchars($pubkey_hash));
}
} else {
respond(400, "Missing public key or public key hash");
}
// Retrieve data by public key hash
$stmt = $db->prepare("SELECT * FROM storage WHERE pubkey_hash = :pubkey_hash");
$stmt->execute([':pubkey_hash' => $pubkey_hash]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$result) {
respond(404, "No data found for this public key");
}
// Set the correct MIME type and output the data without encoding
header('Content-Type: ' . $result['mime_type']);
echo $result['data'];
exit;
}
// Update stored data with a signed transaction
if ($_SERVER['REQUEST_METHOD'] === 'PUT' && $_GET['action'] === 'update') {
parse_str(file_get_contents("php://input"), $_PUT);
$pubkey = $_PUT['pubkey'] ?? null;
$signature = $_PUT['signature'] ?? null;
$data = $_PUT['data'] ?? null;
$mime_type = $_PUT['mime_type'] ?? 'text/plain';
if (!$pubkey || !$signature || !$data) {
respond(400, "Invalid input: missing public key, signature, or data");
}
// Decode base64-encoded public key and signature using safe base64 decoding
$decodedPubkey = safeBase64Decode($pubkey);
$decodedSignature = safeBase64Decode($signature);
if ($decodedPubkey === false || strlen($decodedPubkey) !== 32) {
respond(400, "Invalid public key");
}
if ($decodedSignature === false) {
respond(400, "Invalid signature: not valid base64");
}
$pubkey_hash = bin2hex(sodium_crypto_generichash($decodedPubkey));
if (!sodium_crypto_sign_verify_detached($decodedSignature, $data, $decodedPubkey)) {
respond(400, "Invalid signature");
}
$stmt = $db->prepare("SELECT * FROM storage WHERE pubkey_hash = :pubkey_hash");
$stmt->execute([':pubkey_hash' => $pubkey_hash]);
if (!$stmt->fetch(PDO::FETCH_ASSOC)) {
respond(404, "Space not found");
}
if (strlen($data) > 1024) {
respond(400, "Data exceeds 1k size limit");
}
$stmt = $db->prepare("UPDATE storage SET data = :data, mime_type = :mime_type WHERE pubkey_hash = :pubkey_hash");
$stmt->execute([':data' => $data, ':mime_type' => $mime_type, ':pubkey_hash' => $pubkey_hash]);
respond(200, "Storage updated");
}
// Handle deletion (not in spec, but helpful)
if ($_SERVER['REQUEST_METHOD'] === 'DELETE' && $_GET['action'] === 'delete') {
$pubkey = $_POST['pubkey'] ?? null;
$signature = $_POST['signature'] ?? null;
if (!$pubkey || !$signature) {
respond(400, "Invalid input: missing public key or signature");
}
// Decode base64-encoded public key and signature using safe base64 decoding
$decodedPubkey = safeBase64Decode($pubkey);
$decodedSignature = safeBase64Decode($signature);
if ($decodedPubkey === false || strlen($decodedPubkey) !== 32) {
respond(400, "Invalid public key");
}
if ($decodedSignature === false) {
respond(400, "Invalid signature: not valid base64");
}
$pubkey_hash = bin2hex(sodium_crypto_generichash($decodedPubkey));
if (!CryptoSign::verify_detached($decodedSignature, $pubkey_hash, $decodedPubkey)) {
respond(400, "Invalid signature");
}
$stmt = $db->prepare("DELETE FROM storage WHERE pubkey_hash = :pubkey_hash");
$stmt->execute([':pubkey_hash' => $pubkey_hash]);
respond(200, "Storage deleted");
}
} else {
// If no action is specified, show the contents of main.php
if (file_exists('main.php')) {
include 'main.php';
} else {
respond(400, "No action specified and main.php not found");
}
}
<?php
// Load dependencies
require 'vendor/autoload.php';
use ParagonIE\Sodium\CryptoSign;
// Enable or disable debugging/logging
$debug = isset($_GET['debug']) ? (bool)$_GET['debug'] : true;
if ($debug) {
error_reporting(E_ALL);
ini_set('display_errors', 1);
} else {
error_reporting(0);
ini_set('display_errors', 0);
}
// Database setup function
function initializeDatabase($db) {
$query = "
CREATE TABLE IF NOT EXISTS storage (
pubkey_hash CHAR(64) PRIMARY KEY,
data TEXT NOT NULL,
mime_type VARCHAR(50) NOT NULL,
public_key BLOB NOT NULL
)";
$db->exec($query);
// Check if the 'public_key' column already exists; if not, add it.
$columns = $db->query("PRAGMA table_info(storage)")->fetchAll(PDO::FETCH_ASSOC);
$hasPublicKeyColumn = false;
foreach ($columns as $column) {
if ($column['name'] === 'public_key') {
$hasPublicKeyColumn = true;
break;
}
}
if (!$hasPublicKeyColumn) {
$db->exec("ALTER TABLE storage ADD COLUMN public_key BLOB NOT NULL");
}
}
// Initialize SQLite and auto-create the database and table if they don't exist
$db = new PDO('sqlite:/home/retrodig/1024clubdb/storage.db');
initializeDatabase($db);
// Helper function to respond with JSON
function respond($status, $message, $data = []) {
http_response_code($status);
echo json_encode(['message' => $message, 'data' => $data]);
exit;
}
/**
* Try to decode a Base64 or URL-safe Base64 string (or data: URI).
* Returns decoded bytes on success, or false on failure.
*/
function maybeDecodeBase64($input) {
if ($input === null) return false;
$trim = preg_replace('/\s+/', '', $input);
if (stripos($trim, 'data:') === 0 && ($pos = stripos($trim, ';base64,')) !== false) {
$payload = substr($trim, $pos + 8);
} else {
$payload = $trim;
}
$payload = strtr($payload, '-_', '+/');
$pad = strlen($payload) % 4;
if ($pad) { $payload .= str_repeat('=', 4 - $pad); }
$decoded = base64_decode($payload, true);
if ($decoded === false) { return false; }
// Heuristic to reduce false positives: compare normalized encoding or check for binary-ish data
$re = rtrim(strtr(base64_encode($decoded), '+/', '-_'), '=');
$pa = rtrim(str_replace('=', '', $payload), '=');
if ($re !== $pa) {
$nonPrintable = preg_match_all('/[^\x09\x0A\x0D\x20-\x7E]/', $decoded);
if ($nonPrintable < max(1, (int)(strlen($decoded) * 0.1))) {
return false;
}
}
return $decoded;
}
// Enhance Base64 decoding to handle both raw and URL-safe inputs
function safeBase64Decode($input) {
// First, attempt a direct Base64 decode
$decoded = base64_decode($input, true);
if ($decoded !== false && strlen($decoded) === 32) {
return $decoded;
}
// If the direct decode fails, attempt a URL-safe decode
$urlSafeInput = str_replace(['-', '_'], ['+', '/'], $input);
return base64_decode($urlSafeInput, true);
}
// Handle Base64 encoding safely
function safeBase64Encode($input) {
return str_replace(['+', '/'], ['-', '_'], base64_encode($input));
}
// Check if 'action' is set in the query parameters before proceeding
if (isset($_GET['action'])) {
// Create or get storage space based on pubkey hash
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_GET['action'] === 'create') {
$pubkey = $_POST['pubkey'] ?? null;
if (!$pubkey) {
respond(400, "Missing public key");
}
// Decode the public key, try both raw and URL-safe Base64 decodings
$decodedPubkey = safeBase64Decode($pubkey);
if ($decodedPubkey === false || strlen($decodedPubkey) !== 32) {
respond(400, "Invalid public key");
}
$pubkey_hash = bin2hex(sodium_crypto_generichash($decodedPubkey));
$stmt = $db->prepare("SELECT * FROM storage WHERE pubkey_hash = :pubkey_hash");
$stmt->execute([':pubkey_hash' => $pubkey_hash]);
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
if ($existing) {
respond(200, "Space already exists", ['pubkey_hash' => $pubkey_hash]);
}
$stmt = $db->prepare("INSERT INTO storage (pubkey_hash, data, mime_type, public_key) VALUES (:pubkey_hash, '', 'text/plain', :public_key)");
$stmt->execute([':pubkey_hash' => $pubkey_hash, ':public_key' => $decodedPubkey]);
respond(201, "Space created", ['pubkey_hash' => $pubkey_hash]);
}
// Retrieve stored data using the public key or public key hash
if ($_SERVER['REQUEST_METHOD'] === 'GET' && $_GET['action'] === 'retrieve') {
$pubkey = $_GET['pubkey'] ?? null;
$pubkey_hash = $_GET['pubkey_hash'] ?? null;
if ($pubkey) {
// Replace spaces with '+' to account for unencoded URLs
$pubkey = str_replace(' ', '+', $pubkey);
// Attempt to decode public key (try both standard and URL-safe Base64)
$decodedPubkey = safeBase64Decode($pubkey);
if ($decodedPubkey === false || strlen($decodedPubkey) !== 32) {
respond(400, "Failed to decode public key: " . htmlspecialchars($pubkey));
}
// Hash the decoded public key
$pubkey_hash = bin2hex(sodium_crypto_generichash($decodedPubkey));
} elseif ($pubkey_hash) {
// Validate the provided public key hash (must be 64 characters long, hex format)
if (!ctype_xdigit($pubkey_hash) || strlen($pubkey_hash) !== 64) {
respond(400, "Invalid public key hash: " . htmlspecialchars($pubkey_hash));
}
} else {
respond(400, "Missing public key or public key hash");
}
// Retrieve data by public key hash
$stmt = $db->prepare("SELECT * FROM storage WHERE pubkey_hash = :pubkey_hash");
$stmt->execute([':pubkey_hash' => $pubkey_hash]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$result) {
respond(404, "No data found for this public key");
}
// Set the correct MIME type and output the data without encoding
header('Content-Type: ' . $result['mime_type']);
echo $result['data'];
exit;
}
// Update stored data with a signed transaction
if ($_SERVER['REQUEST_METHOD'] === 'PUT' && $_GET['action'] === 'update') {
parse_str(file_get_contents("php://input"), $_PUT);
$pubkey = $_PUT['pubkey'] ?? null;
$signature = $_PUT['signature'] ?? null;
$data = $_PUT['data'] ?? null;
$mime_type = $_PUT['mime_type'] ?? 'text/plain';
if (!$pubkey || !$signature || !$data) {
respond(400, "Invalid input: missing public key, signature, or data");
}
// Decode base64-encoded public key and signature using safe base64 decoding
$decodedPubkey = safeBase64Decode($pubkey);
$decodedSignature = safeBase64Decode($signature);
if ($decodedPubkey === false || strlen($decodedPubkey) !== 32) {
respond(400, "Invalid public key");
}
if ($decodedSignature === false) {
respond(400, "Invalid signature: not valid base64");
}
$pubkey_hash = bin2hex(sodium_crypto_generichash($decodedPubkey));
// If data looks like base64 (or data:...;base64,...) decode it for size check and storage.
$decodedData = maybeDecodeBase64($data);
$isBase64Payload = ($decodedData !== false);
// Verify signature. First try verifying exactly what we received (backwards compatibility).
$sigOk = sodium_crypto_sign_verify_detached($decodedSignature, $data, $decodedPubkey);
// If that fails and it looked like base64, also try verifying the decoded bytes (clients that sign raw bytes).
if (!$sigOk && $isBase64Payload) {
$sigOk = sodium_crypto_sign_verify_detached($decodedSignature, $decodedData, $decodedPubkey);
}
if (!$sigOk) {
respond(403, "Invalid signature");
}
// Enforce 1k limit against decoded bytes when base64 was used; otherwise against the raw payload.
$dataForLimit = $isBase64Payload ? $decodedData : $data;
if (strlen($dataForLimit) > 1024) {
respond(400, "Data exceeds 1k size limit");
}
// Choose what we store: decoded bytes when base64 was used (so retrieval returns raw), otherwise raw text.
$dataToStore = $isBase64Payload ? $decodedData : $data;
// Normalize MIME type for common images if a data URI was provided.
if ($isBase64Payload && isset($_PUT['mime_type']) && stripos($_PUT['mime_type'], 'image/') === 0) {
$mime_type = $_PUT['mime_type'];
}
// Persist
$stmt = $db->prepare("UPDATE storage SET data = :data, mime_type = :mime_type WHERE pubkey_hash = :pubkey_hash");
$stmt->execute([':data' => $dataToStore, ':mime_type' => $mime_type, ':pubkey_hash' => $pubkey_hash]);
respond(200, "Storage updated");
}
// Handle deletion (not in spec, but helpful)
if ($_SERVER['REQUEST_METHOD'] === 'DELETE' && $_GET['action'] === 'delete') {
$pubkey = $_POST['pubkey'] ?? null;
$signature = $_POST['signature'] ?? null;
if (!$pubkey || !$signature) {
respond(400, "Invalid input: missing public key or signature");
}
// Decode base64-encoded public key and signature using safe base64 decoding
$decodedPubkey = safeBase64Decode($pubkey);
$decodedSignature = safeBase64Decode($signature);
if ($decodedPubkey === false || strlen($decodedPubkey) !== 32) {
respond(400, "Invalid public key");
}
if ($decodedSignature === false) {
respond(400, "Invalid signature: not valid base64");
}
$pubkey_hash = bin2hex(sodium_crypto_generichash($decodedPubkey));
if (!CryptoSign::verify_detached($decodedSignature, $pubkey_hash, $decodedPubkey)) {
respond(400, "Invalid signature");
}
$stmt = $db->prepare("DELETE FROM storage WHERE pubkey_hash = :pubkey_hash");
$stmt->execute([':pubkey_hash' => $pubkey_hash]);
respond(200, "Storage deleted");
}
} else {
// If no action is specified, show the contents of main.php
if (file_exists('main.php')) {
include 'main.php';
} else {
respond(400, "No action specified and main.php not found");
}
}
?>

View File

@ -16,7 +16,8 @@
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="https://github.com/the1024club/the1024.club" target="_blank">Source Code</a></li>
</ul>
</nav>
@ -168,7 +169,7 @@ php 1kb_client.php retrieve public_key.pem
<footer>
<p class="license-title">The MIT License (MIT)</p>
<p class="license-copyright">©2020 The 1024 Club Developers (see AUTHORS.txt)</p>
<p class="license-copyright">©2024 The 1024 Club Developers <a href="https://github.com/the1024club/the1024.club" target="_blank">Source Code</a></p>
<p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:</p>
<p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p>
<p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>

BIN
utils.zip Normal file

Binary file not shown.

View File

@ -141,4 +141,4 @@ elseif ($operation === 'retrieve') {
else {
showUsage();
}
?>
?>

18
utils/verify_keys.php Normal file
View File

@ -0,0 +1,18 @@
<?php
// Load Sodium extension
require __DIR__ . '/../vendor/autoload.php'; // Adjust the path to the autoload file
// Base64 decode the public and private keys
$publicKey = base64_decode('0HCO9Tm3JvvbK+J93PStb4/23HSxEZS9WUrWSjmLIz4=');
$privateKey = base64_decode('s1cAUQfbxa6Q8lJxn90N6aKER17Ng9tD/gHpX+8x6drQcI71Obcm+9sr4n3c9K1vj/bcdLERlL1ZStZKOYsjPg==');
// Derive the public key from the private key to check if they match
$derivedPublicKey = sodium_crypto_sign_publickey_from_secretkey($privateKey);
// Compare the derived public key with the provided public key
if (hash_equals($publicKey, $derivedPublicKey)) {
echo "The public and private key pair match!" . PHP_EOL;
} else {
echo "The public and private key pair do NOT match!" . PHP_EOL;
}
?>