Upload files to "src/main/resources/static"

This commit is contained in:
2026-01-04 11:20:48 +00:00
parent f27caffcae
commit 8d64305403
3 changed files with 1008 additions and 0 deletions

View File

@@ -0,0 +1,338 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Secure Messaging Client</title>
<style>
body { font-family: sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
h2 { border-bottom: 2px solid #ccc; padding-bottom: 5px; }
label { display: block; margin-top: 10px; font-weight: bold; }
input[type="text"], textarea, select { width: 100%; padding: 8px; margin-top: 5px; box-sizing: border-box; }
button { padding: 10px 15px; margin-top: 10px; cursor: pointer; background-color: #007bff; color: white; border: none; border-radius: 4px; }
button:hover { background-color: #0056b3; }
#keys { background-color: #f4f4f4; padding: 15px; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 20px; }
.key-pair div { margin-bottom: 15px; }
#log { margin-top: 20px; padding: 10px; background-color: #e9ecef; border: 1px solid #ced4da; height: 150px; overflow-y: scroll; white-space: pre-wrap; }
.message-box { border: 1px solid #ddd; padding: 15px; margin-bottom: 10px; border-radius: 4px; }
.encrypted-message { color: #888; font-style: italic; font-size: 0.9em; }
.decrypted-message { color: #000; font-weight: bold; }
</style>
</head>
<body>
<h1>Encrypted Messaging Client</h1>
<div id="keys">
<h2>🔑 Your Identity (Alice)</h2>
<p>Generate a new RSA key pair for Alice, or paste existing keys (in Base64 X.509/PKCS#8 format).</p>
<button id="generateKeysBtn">1. Generate New Key Pair</button>
<div class="key-pair">
<label for="publicKey">Your Public Key (X.509 Base64 - for sharing):</label>
<textarea id="publicKey" rows="5" placeholder="Public Key"></textarea>
<label for="privateKey">Your Private Key (PKCS#8 Base64 - MUST BE KEPT SECRET):</label>
<textarea id="privateKey" rows="5" placeholder="Private Key"></textarea>
</div>
<p>Use a single line for the keys to match the server's clean processing of Base64 strings.</p>
</div>
<hr>
<div id="send">
<h2>✉️ Send Message</h2>
<label for="recipientKey">Recipient's Public Key (The key you encrypt FOR):</label>
<textarea id="recipientKey" rows="3" placeholder="Paste Recipient's Public Key here (e.g., Bob's Key)"></textarea>
<label for="messageContent">Message Content (Plaintext):</label>
<textarea id="messageContent" rows="3" placeholder="Type your secret message here..."></textarea>
<button onclick="sendMessage()">2. Encrypt, Sign, & Send Message</button>
</div>
<hr>
<div id="receive">
<h2>📥 Retrieve Messages</h2>
<p>The server logic requires a signature even for a retrieve request.</p>
<button onclick="retrieveMessages()">3. Retrieve & Decrypt Messages</button>
<div id="incomingMessages">
<p>Decrypted messages will appear here.</p>
</div>
</div>
<hr>
<h2>Console Log</h2>
<div id="log">Awaiting action...</div>
<script>
// --- CONFIGURATION ---
const SERVER_URL = "http://localhost:8080/messages"; // **CHANGE THIS TO YOUR SERVER URL**
// RSA Algorithm Constants for Web Crypto API
const SIGN_ALGORITHM = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
const ENCRYPT_ALGORITHM = { name: "RSA-OAEP", hash: "SHA-256" };
const KEY_PARAMS = {
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537
hash: "SHA-256"
};
const KEY_USAGES = ["encrypt", "decrypt"];
// Imported keys stored internally as CryptoKey objects
let cryptoKeyPair = null;
// --- UTILITY FUNCTIONS ---
/** Logs a message to the console and the HTML log area. */
function log(message) {
console.log(message);
const logDiv = document.getElementById('log');
logDiv.innerHTML = new Date().toLocaleTimeString() + " | " + message + "\n" + logDiv.innerHTML;
}
/** Converts ArrayBuffer to Base64 (needed for crypto exports/imports). */
function bufferToBase64(buffer) {
return btoa(String.fromCharCode.apply(null, new Uint8Array(buffer)));
}
/** Converts Base64 to ArrayBuffer (needed for crypto exports/imports). */
function base64ToBuffer(b64) {
return Uint8Array.from(atob(b64), c => c.charCodeAt(0));
}
/** Converts a string to ArrayBuffer for signing/encrypting. */
function strToBuffer(str) {
return new TextEncoder().encode(str);
}
/** Converts ArrayBuffer to string for decryption result. */
function bufferToStr(buffer) {
return new TextDecoder().decode(buffer);
}
// --- KEY MANAGEMENT ---
document.getElementById('generateKeysBtn').onclick = async () => {
try {
// Generate a full key pair suitable for both signing and encryption/decryption
cryptoKeyPair = await crypto.subtle.generateKey(KEY_PARAMS, true, KEY_USAGES);
// Export Public Key (SPKI format, required by Java's X509EncodedKeySpec)
const pubBuffer = await crypto.subtle.exportKey('spki', cryptoKeyPair.publicKey);
document.getElementById('publicKey').value = bufferToBase64(pubBuffer);
// Export Private Key (PKCS8 format, required for server-side compatibility)
const privBuffer = await crypto.subtle.exportKey('pkcs8', cryptoKeyPair.privateKey);
document.getElementById('privateKey').value = bufferToBase64(privBuffer);
log("New key pair generated successfully. Public key is ready for sharing.");
} catch (e) {
log("ERROR: Could not generate keys. " + e.message);
}
};
/** Imports a public key from Base64 string for ENCRYPTION. */
async function importRecipientPublicKey(b64Key) {
const keyBuffer = base64ToBuffer(b64Key.replaceAll(/\s/g, ''));
return crypto.subtle.importKey(
'spki', // Use 'spki' for X.509 public key format
keyBuffer,
ENCRYPT_ALGORITHM,
false, // not extractable
['encrypt']
);
}
/** Imports the user's private key from Base64 string for DECRYPTION. */
async function importUserPrivateKey(b64Key) {
const keyBuffer = base64ToBuffer(b64Key.replaceAll(/\s/g, ''));
return crypto.subtle.importKey(
'pkcs8', // Use 'pkcs8' for private key format
keyBuffer,
ENCRYPT_ALGORITHM,
false, // not extractable
['decrypt']
);
}
/** Imports the user's private key from Base64 string for SIGNING. */
async function importUserSigningKey(b64Key) {
const keyBuffer = base64ToBuffer(b64Key.replaceAll(/\s/g, ''));
return crypto.subtle.importKey(
'pkcs8',
keyBuffer,
SIGN_ALGORITHM,
false,
['sign']
);
}
// --- MESSAGE LOGIC ---
/** Canonicalizes the set of messages for deterministic signing. */
function getEnvelopeStringToSign(messages, publicKey) {
const messageData = messages
.map(m => m.encryptedMessage + ";" + m.senderPublicKey + ";" + m.recipientPublicKey + ";" + m.timestamp)
.sort() // CRUCIAL: Must sort for deterministic signing
.join("|");
return messageData + ";" + publicKey;
}
/** Sends a message to the server. */
async function sendMessage() {
const messageContent = document.getElementById('messageContent').value.trim();
const recipientKey = document.getElementById('recipientKey').value.trim();
const senderKey = document.getElementById('publicKey').value.trim();
const privateKeyB64 = document.getElementById('privateKey').value.trim();
if (!messageContent || !recipientKey || !senderKey || !privateKeyB64) {
return log("ERROR: All fields (message, recipient key, your public/private keys) must be filled.");
}
try {
// 1. Import Recipient's Public Key for ENCRYPTION
const pubKey = await importRecipientPublicKey(recipientKey);
// 2. Encrypt the Message
const encryptedBuffer = await crypto.subtle.encrypt(
ENCRYPT_ALGORITHM,
pubKey,
strToBuffer(messageContent)
);
const encryptedMessage = bufferToBase64(encryptedBuffer);
log("Message encrypted. Ciphertext size: " + encryptedMessage.length + " bytes.");
// 3. Construct the Message and Envelope DTOs
const message = {
encryptedMessage: encryptedMessage,
senderPublicKey: senderKey,
recipientPublicKey: recipientKey,
timestamp: new Date().toISOString()
};
const envelope = {
messages: [message],
publicKey: senderKey,
signature: ""
};
// 4. Canonicalize and Sign the Envelope
const stringToSign = getEnvelopeStringToSign(envelope.messages, envelope.publicKey);
const signingKey = await importUserSigningKey(privateKeyB64);
const signatureBuffer = await crypto.subtle.sign(
SIGN_ALGORITHM,
signingKey,
strToBuffer(stringToSign)
);
envelope.signature = bufferToBase64(signatureBuffer);
log("Envelope signed. Signature length: " + envelope.signature.length + " bytes.");
// 5. Send to Server
const response = await fetch(SERVER_URL + "/receiveMessage", {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(envelope)
});
const result = await response.text();
log(`Server Response (SEND): ${result}`);
} catch (e) {
log("FATAL ERROR during send: " + e.message);
console.error(e);
}
}
/** Retrieves and decrypts messages from the server. */
async function retrieveMessages() {
const userKey = document.getElementById('publicKey').value.trim();
const privateKeyB64 = document.getElementById('privateKey').value.trim();
const incomingDiv = document.getElementById('incomingMessages');
incomingDiv.innerHTML = ''; // Clear previous messages
if (!userKey || !privateKeyB64) {
return log("ERROR: Your public and private keys must be filled to retrieve messages.");
}
try {
// 1. Prepare Envelope for Retrieve Request (uses an empty message set)
const envelope = {
messages: [], // Message list is empty for a retrieval request
publicKey: userKey,
signature: ""
};
// 2. Canonicalize and Sign the Envelope
const stringToSign = getEnvelopeStringToSign(envelope.messages, envelope.publicKey);
const signingKey = await importUserSigningKey(privateKeyB64);
const signatureBuffer = await crypto.subtle.sign(
SIGN_ALGORITHM,
signingKey,
strToBuffer(stringToSign)
);
envelope.signature = bufferToBase64(signatureBuffer);
// 3. Send to Server
const response = await fetch(SERVER_URL + "/retrieveMessages", {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(envelope)
});
if (response.status !== 200) {
return log(`Server Error (${response.status}): ${await response.text()}`);
}
const messages = await response.json();
if (!messages || messages.length === 0) {
incomingDiv.innerHTML = "<p>No new messages found.</p>";
return log("Successfully checked for messages. None found.");
}
log(`Retrieved ${messages.length} message(s). Attempting decryption...`);
// 4. Import User's Private Key for DECRYPTION
const privateKey = await importUserPrivateKey(privateKeyB64);
// 5. Decrypt Each Message
for (const msg of messages) {
const box = document.createElement('div');
box.className = 'message-box';
let decryptedContent = "--- Decryption Failed ---";
try {
const encryptedBuffer = base64ToBuffer(msg.encryptedMessage);
const decryptedBuffer = await crypto.subtle.decrypt(
ENCRYPT_ALGORITHM,
privateKey,
encryptedBuffer
);
decryptedContent = bufferToStr(decryptedBuffer);
} catch (e) {
log(`Decryption failed for a message from ${msg.senderPublicKey}: ${e.message}`);
}
box.innerHTML = `
<p><strong>Sender:</strong> ${msg.senderPublicKey.substring(0, 30)}...</p>
<p><strong>Timestamp:</strong> ${new Date(msg.timestamp).toLocaleString()}</p>
<p class="encrypted-message">Encrypted Data: ${msg.encryptedMessage.substring(0, 50)}...</p>
<p><strong>Decrypted Message:</strong> <span class="decrypted-message">${decryptedContent}</span></p>
`;
incomingDiv.appendChild(box);
}
log(`Successfully retrieved and processed ${messages.length} message(s).`);
} catch (e) {
log("FATAL ERROR during retrieval: " + e.message);
console.error(e);
}
}
</script>
</body>
</html>