Upload files to "src/main/resources/static"
This commit is contained in:
338
src/main/resources/static/client3.html
Normal file
338
src/main/resources/static/client3.html
Normal 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>
|
||||
Reference in New Issue
Block a user