Generate OpenPGP Key Pair
+Create a new key pair for secure communication
+diff --git a/app.py b/app.py index 0e4eb4b..4f08c01 100644 --- a/app.py +++ b/app.py @@ -1,6 +1,6 @@ # gpg_api.py -from flask import Flask, Request, request, jsonify, send_file +from flask import Flask, Request, render_template, request, jsonify, send_file import gnupg import tempfile import os @@ -74,27 +74,12 @@ def find_key_by_email(email, secret=False): # @app.route("/api/setup/gnupg", methods=["GET"]) # def setup_gnupg(): -# """Check if GnuPG is available through python-gnupg""" -# try: -# # Try to get version info -# version_info = gpg_instance.version -# logger.info("GnuPG accessible through python-gnupg") -# return jsonify( -# { -# "status": "GnuPG is available through python-gnupg", -# "version": version_info, -# } -# ) -# except Exception as e: -# logger.warning(f"GnuPG not accessible: {e}") -# return ( -# jsonify( -# { -# "error": "GnuPG is not accessible. Please ensure it's properly installed." -# } -# ), -# 404, -# ) + + +@app.route("/") +def index(): + """Render the main application page""" + return render_template("index.html") @app.route("/api/generate-key", methods=["POST"]) diff --git a/static/script.js b/static/script.js new file mode 100644 index 0000000..19c1a36 --- /dev/null +++ b/static/script.js @@ -0,0 +1,700 @@ +// Application State +let currentTab = "generate" +let selectedFiles = [] +let isProcessing = false + +// DOM Elements +const elements = { + // Tabs + tabGenerate: document.getElementById("tab-generate"), + tabDecrypt: document.getElementById("tab-decrypt"), + generateSection: document.getElementById("generate-section"), + decryptSection: document.getElementById("decrypt-section"), + + // Key Generation Form + keyGenerationForm: document.getElementById("key-generation-form"), + nameInput: document.getElementById("name"), + emailInput: document.getElementById("email"), + passphraseInput: document.getElementById("passphrase"), + confirmPassphraseInput: document.getElementById("confirm-passphrase"), + commentInput: document.getElementById("comment"), + generateBtn: document.getElementById("generate-btn"), + generationStatus: document.getElementById("generation-status"), + loadingStatus: document.getElementById("loading-status"), + successStatus: document.getElementById("success-status"), + errorStatus: document.getElementById("error-status"), + errorMessage: document.getElementById("error-message"), + + // File Decryption + dropZone: document.getElementById("drop-zone"), + fileInput: document.getElementById("file-input"), + selectFilesBtn: document.getElementById("select-files-btn"), + fileList: document.getElementById("file-list"), + filesContainer: document.getElementById("files-container"), + decryptionForm: document.getElementById("decryption-form"), + privateKeyInput: document.getElementById("private-key"), + keyPassphraseInput: document.getElementById("key-passphrase"), + decryptBtn: document.getElementById("decrypt-btn"), + decryptionStatus: document.getElementById("decryption-status"), + progressBar: document.getElementById("progress-bar"), + decryptionMessage: document.getElementById("decryption-message"), + decryptionResults: document.getElementById("decryption-results"), +} + +// Initialize Application +document.addEventListener("DOMContentLoaded", function () { + initializeEventListeners() + setupFormValidation() + setupFileHandling() +}) + +// Event Listeners +function initializeEventListeners() { + // Tab Navigation + elements.tabGenerate.addEventListener("click", () => switchTab("generate")) + elements.tabDecrypt.addEventListener("click", () => switchTab("decrypt")) + + // Key Generation Form + elements.keyGenerationForm.addEventListener("submit", handleKeyGeneration) + + // File Upload + elements.selectFilesBtn.addEventListener("click", () => + elements.fileInput.click() + ) + elements.fileInput.addEventListener("change", handleFileSelection) + + // Drag and Drop + elements.dropZone.addEventListener("dragover", handleDragOver) + elements.dropZone.addEventListener("dragleave", handleDragLeave) + elements.dropZone.addEventListener("drop", handleFileDrop) + + // Decryption Form + elements.decryptionForm.addEventListener("submit", handleFileDecryption) + + // Keyboard accessibility for drop zone + elements.dropZone.addEventListener("keydown", (e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault() + elements.fileInput.click() + } + }) +} + +// Tab Management +function switchTab(tabName) { + currentTab = tabName + + // Update tab buttons + document.querySelectorAll(".tab-button").forEach((btn) => { + btn.classList.remove("active") + btn.classList.add("text-white/70") + }) + + const activeTab = document.getElementById(`tab-${tabName}`) + activeTab.classList.add("active") + activeTab.classList.remove("text-white/70") + activeTab.classList.add("text-white") + + // Update tab content + elements.generateSection.classList.toggle("hidden", tabName !== "generate") + elements.decryptSection.classList.toggle("hidden", tabName !== "decrypt") + + // Reset states when switching tabs + resetFormStates() +} + +// Form Validation +function setupFormValidation() { + // Real-time validation + elements.nameInput.addEventListener("input", () => validateField("name")) + elements.emailInput.addEventListener("input", () => validateField("email")) + elements.passphraseInput.addEventListener("input", () => + validateField("passphrase") + ) + elements.confirmPassphraseInput.addEventListener("input", () => + validateField("confirm-passphrase") + ) + elements.privateKeyInput.addEventListener("input", () => + validateField("private-key") + ) + elements.keyPassphraseInput.addEventListener("input", () => + validateField("key-passphrase") + ) +} + +function validateField(fieldName) { + const field = document.getElementById(fieldName) + const errorElement = document.getElementById(`${fieldName}-error`) + let isValid = true + let errorMessage = "" + + switch (fieldName) { + case "name": + if (!field.value.trim()) { + isValid = false + errorMessage = "Full name is required" + } + break + + case "email": + if (!field.value.trim()) { + isValid = false + errorMessage = "Email address is required" + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(field.value)) { + isValid = false + errorMessage = "Please enter a valid email address" + } + break + + case "passphrase": + if (!field.value) { + isValid = false + errorMessage = "Passphrase is required" + } else if (field.value.length < 8) { + isValid = false + errorMessage = "Passphrase must be at least 8 characters long" + } + break + + case "confirm-passphrase": + if (field.value !== elements.passphraseInput.value) { + isValid = false + errorMessage = "Passphrases do not match" + } + break + + case "private-key": + if (!field.value.trim()) { + isValid = false + errorMessage = "Private key is required" + } else if ( + !field.value.includes("-----BEGIN PGP PRIVATE KEY BLOCK-----") + ) { + isValid = false + errorMessage = "Please enter a valid PGP private key" + } + break + + case "key-passphrase": + if (!field.value) { + isValid = false + errorMessage = "Key passphrase is required" + } + break + } + + // Update field styling and error message + if (isValid) { + field.classList.remove("border-red-500") + field.classList.add("border-gray-300") + errorElement.classList.add("hidden") + errorElement.textContent = "" + } else { + field.classList.add("border-red-500") + field.classList.remove("border-gray-300") + errorElement.classList.remove("hidden") + errorElement.textContent = errorMessage + } + + return isValid +} + +function validateForm(formType) { + let isValid = true + + if (formType === "generation") { + isValid = validateField("name") && isValid + isValid = validateField("email") && isValid + isValid = validateField("passphrase") && isValid + isValid = validateField("confirm-passphrase") && isValid + } else if (formType === "decryption") { + isValid = validateField("private-key") && isValid + isValid = validateField("key-passphrase") && isValid + } + + return isValid +} + +// Key Generation +async function handleKeyGeneration(e) { + e.preventDefault() + + if (isProcessing || !validateForm("generation")) { + return + } + + isProcessing = true + showGenerationStatus("loading") + + const formData = { + name: elements.nameInput.value.trim(), + email: elements.emailInput.value.trim(), + passphrase: elements.passphraseInput.value, + comment: elements.commentInput.value.trim() || undefined, + } + + try { + // Simulate API call (replace with actual API endpoint) + const responseKeyGeneration = await keyGenerationAPI(formData) + + if (!responseKeyGeneration.success) { + console.error("Key generation failed:", responseKeyGeneration.error) + throw new Error("Key generation failed") + } + // Auto-download public key + const responsePublicKey = await publicKeyDownloadAPI(formData.email) + if (!responsePublicKey.ok) { + const errorData = await response.json() + console.error("Failed to download public key:", errorData) + throw new Error("Failed to download public key") + } + downloadFile( + await responsePublicKey.blob(), + responsePublicKey.filename, + responsePublicKey.mimetype + ) + + showGenerationStatus("success") + + // Reset form after successful generation + setTimeout(() => { + elements.keyGenerationForm.reset() + showGenerationStatus("hidden") + }, 3000) + } catch (error) { + console.error("Key generation error:", error) + showGenerationStatus("error", error.message) + } finally { + isProcessing = false + } +} + +function showGenerationStatus(type, message = "") { + elements.generationStatus.classList.remove("hidden") + elements.loadingStatus.classList.add("hidden") + elements.successStatus.classList.add("hidden") + elements.errorStatus.classList.add("hidden") + + if (type === "loading") { + elements.loadingStatus.classList.remove("hidden") + elements.generateBtn.disabled = true + elements.generateBtn.innerHTML = + 'Generating...' + } else if (type === "success") { + elements.successStatus.classList.remove("hidden") + elements.generateBtn.disabled = false + elements.generateBtn.innerHTML = + 'Generate Key Pair' + } else if (type === "error") { + elements.errorStatus.classList.remove("hidden") + elements.errorMessage.textContent = message + elements.generateBtn.disabled = false + elements.generateBtn.innerHTML = + 'Generate Key Pair' + } else { + elements.generationStatus.classList.add("hidden") + elements.generateBtn.disabled = false + elements.generateBtn.innerHTML = + 'Generate Key Pair' + } +} + +// File Handling +function setupFileHandling() { + // Prevent default drag behaviors + ;["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => { + document.addEventListener(eventName, preventDefaults, false) + }) + + function preventDefaults(e) { + e.preventDefault() + e.stopPropagation() + } +} + +function handleDragOver(e) { + e.preventDefault() + elements.dropZone.classList.add("drag-over") +} + +function handleDragLeave(e) { + e.preventDefault() + elements.dropZone.classList.remove("drag-over") +} + +function handleFileDrop(e) { + e.preventDefault() + elements.dropZone.classList.remove("drag-over") + + const files = Array.from(e.dataTransfer.files) + processFiles(files) +} + +function handleFileSelection(e) { + const files = Array.from(e.target.files) + processFiles(files) +} + +function processFiles(files) { + const validFiles = [] + const allowedTypes = [".asc", ".gpg", ".pgp", ".txt"] + const maxSize = 10 * 1024 * 1024 // 10MB + + files.forEach((file) => { + const extension = "." + file.name.split(".").pop().toLowerCase() + + if (!allowedTypes.includes(extension)) { + showNotification( + `File "${file.name}" has an unsupported format.`, + "error" + ) + return + } + + if (file.size > maxSize) { + showNotification(`File "${file.name}" is too large (max 10MB).`, "error") + return + } + + validFiles.push(file) + }) + + if (validFiles.length > 0) { + selectedFiles = validFiles + displaySelectedFiles() + elements.decryptionForm.classList.remove("hidden") + } +} + +function displaySelectedFiles() { + elements.fileList.classList.remove("hidden") + elements.filesContainer.innerHTML = "" + + selectedFiles.forEach((file, index) => { + const fileElement = document.createElement("div") + fileElement.className = + "flex items-center justify-between p-3 bg-gray-50 rounded-lg" + fileElement.innerHTML = ` +
+ Generate OpenPGP key pairs and decrypt files securely. + All cryptographic operations are performed via secure API endpoints. +
+Create a new key pair for secure communication
+