aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md38
-rw-r--r--get.html131
-rw-r--r--index.html143
-rw-r--r--paste-cgi.py142
-rw-r--r--script.js230
5 files changed, 302 insertions, 382 deletions
diff --git a/README.md b/README.md
index d82d685..76581d8 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# Paste-cgi
## Description
-This is a dirty pastebin done in CGI. It is completely client-side encrypted and server only stored encrypted paste.
+This is a dirty pastebin done in CGI. It is completely client-side encrypted and server only stores encrypted paste(Title is not encrypted).
## Images
![Index](docs/Index.png)
@@ -10,24 +10,44 @@ This is a dirty pastebin done in CGI. It is completely client-side encrypted and
## Nginx Config
```
server {
+ server_name paste.example.com;
+ root /example/root/dir;
- server_name SERVERURL;
- root ROOTFOLDER;
+ # Global basic Auth
+ auth_basic "Restricted Content";
+ auth_basic_user_file /etc/nginx/.htpasswd;
- location / {
- auth_basic "Restricted Content"; # Basic Auth
- auth_basic_user_file /etc/nginx/.htpasswd; # Basic Auth
+ # Server static files
+ location = / {
+ try_files /index.html =404;
+ }
+
+ location = /get {
+ try_files /get.html =404;
+ }
+
+ location ~* \.(css|js|svg)$ {
+ try_files $uri =404;
+ access_log off;
+ expires 30d;
+ }
+
+ # Handle cgi routes
+ location ~ ^/(paste|submit)$ {
include fastcgi_params;
- fastcgi_param SCRIPT_FILENAME PATH TO SCRIPT;
+ fastcgi_param SCRIPT_FILENAME /example/path/to/cgi;
fastcgi_param PATH_INFO $uri;
fastcgi_param QUERY_STRING $args;
fastcgi_param HTTP_HOST $server_name;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param CONTENT_TYPE $content_type;
- fastcgi_param ALLOWED_DIR PATH WHERE THE ALLOWED DIRECTORY IS TO CHANGE FILES;
+ fastcgi_param ALLOWED_DIR /example/allowed/dir;
fastcgi_pass unix:/run/fcgiwrap.socket;
}
-
+ # Deny everything else
+ location / {
+ return 403;
+ }
listen [::]:443 ssl; # managed by Certbot
listen 443 ssl; # managed by Certbot
diff --git a/get.html b/get.html
index 9799eba..37768b0 100644
--- a/get.html
+++ b/get.html
@@ -3,15 +3,16 @@
<head>
<meta charset="UTF-8">
- <meta name="description" content="AES-256 Encryption Example">
- <title>Secure Paste</title>
+ <title>paste-cgi</title>
<link rel="icon" href="./favicon.svg" type="image/svg+xml">
<link rel="stylesheet" href="./pico.min.css">
</head>
<body>
<main class="container">
- <h1>paste-cgi</h1>
+ <a href="/">
+ <h1>paste-cgi</h1>
+ </a>
<form id="cryptoForm">
<h3 id="title">Title</h3>
@@ -30,129 +31,7 @@
<button type="button" onclick="copyPaste()">Copy</button>
</div>
</main>
-
- <script>
- let globalData;
- const pasteTypeConstant = "pasteType";
- const passwordFieldConstant = "passwordField";
- const passwordTypeConstant = "password";
- const decryptButtonConstant = "decryptBtn";
- const plainTypeConstant = "plain";
- const titleConstant = "title";
- const expirationConstant = "expiration";
- const plaintextConstant = "plaintext";
- const decryptedOutputConstant = "decryptedOutput"
- const pasteUrlSectionConstant = "pasteUrlSection"
-
- window.onload = async function () {
- const urlParams = new URLSearchParams(window.location.search);
- const id = urlParams.get('id');
-
- const fetchUrl = `${window.location.origin}/paste?id=${id}`;
-
- try {
- const response = await fetch(fetchUrl);
-
- if (!response.ok) {
- throw new Error('Failed to fetch data');
- }
-
- const data = await response.json();
-
- globalData = data;
- togglePasteType()
- } catch (error) {
- alert("NO File Found")
- console.error('Error fetching data:', error);
- }
- };
-
- function copyPaste() {
- var copyText = document.getElementById(decryptedOutputConstant);
-
- copyText.select();
- copyText.setSelectionRange(0, 99999);
-
- navigator.clipboard.writeText(copyText.value);
- }
- function togglePasteType() {
- const decryptButton = document.getElementById(decryptButtonConstant);
- const passwordField = document.getElementById(passwordFieldConstant);
- document.getElementById(titleConstant).textContent = globalData.title;
- if (globalData.type === passwordTypeConstant) {
- passwordField.style.display = 'block';
- decryptButton.style.display = 'block';
- } else {
- decryptButton.style.display = 'none';
- passwordField.style.display = 'none';
- document.getElementById(decryptedOutputConstant).textContent = globalData.pasted_text
- document.getElementById(pasteUrlSectionConstant).style.display = 'block';
- }
- }
- async function deriveKey(password, salt) {
- let encodedPassword = new TextEncoder().encode(password);
- let baseKey = await window.crypto.subtle.importKey(
- "raw",
- encodedPassword,
- { name: "PBKDF2" },
- false,
- ["deriveKey"],
- );
-
- let derivedKey = await window.crypto.subtle.deriveKey(
- {
- name: "PBKDF2",
- salt: salt,
- iterations: 600000,
- hash: "SHA-256",
- },
- baseKey,
- { name: "AES-GCM", length: 256 },
- true,
- ["encrypt", "decrypt"],
- );
-
- return derivedKey;
- }
-
- async function decryptData(encryptedData, password) {
- let { ciphertext, iv, authTag, salt } = encryptedData;
- let key = await deriveKey(password, salt);
-
- let dataWithAuthTag = new Uint8Array(ciphertext.length + authTag.length);
- dataWithAuthTag.set(ciphertext, 0);
- dataWithAuthTag.set(authTag, ciphertext.length);
-
- let decryptedContent = await window.crypto.subtle.decrypt(
- { name: "AES-GCM", iv: iv, tagLength: 128 },
- key,
- dataWithAuthTag,
- );
-
- return new TextDecoder().decode(decryptedContent);
- }
- async function handleDecrypt() {
- let base64 = globalData.pasted_text;
- let password = document.getElementById(passwordTypeConstant).value;
- if (!base64 || !password) return alert("Enter encrypted text and password.");
- try {
-
- let parsed = JSON.parse(atob(base64));
- let encryptedData = {
- salt: new Uint8Array(Object.values(parsed.salt)),
- iv: new Uint8Array(Object.values(parsed.iv)),
- authTag: new Uint8Array(Object.values(parsed.authTag)),
- ciphertext: new Uint8Array(Object.values(parsed.ciphertext)),
- };
-
- let decrypted = await decryptData(encryptedData, password);
- document.getElementById("decryptedOutput").value = decrypted;
- document.getElementById(pasteUrlSectionConstant).style.display = 'block';
- } catch (err) {
- document.getElementById("decryptedOutput").value = "❌ Decryption failed.";
- }
- }
- </script>
+ <script src="/script.js"></script>
</body>
</html> \ No newline at end of file
diff --git a/index.html b/index.html
index f4fe7a3..cafb087 100644
--- a/index.html
+++ b/index.html
@@ -3,15 +3,16 @@
<head>
<meta charset="UTF-8">
- <meta name="description" content="AES-256 Encryption Example">
- <title>Secure Paste</title>
+ <title>paste-cgi</title>
<link rel="icon" href="./favicon.svg" type="image/svg+xml">
<link rel="stylesheet" href="./pico.min.css">
</head>
<body>
<main class="container">
- <h1>paste-cgi</h1>
+ <a href="/">
+ <h1>paste-cgi</h1>
+ </a>
<form id="cryptoForm">
<label for="title">Title</label>
@@ -54,141 +55,7 @@
<button type="button" onclick="copyPaste()">Copy</button>
</div>
</main>
-
- <script>
- const pasteTypeConstant = "pasteType";
- const passwordFieldConstant = "passwordField";
- const passwordTypeConstant = "password";
- const plainTypeConstant = "plain";
- const titleConstant = "title";
- const expirationConstant = "expiration";
- const plaintextConstant = "plaintext";
- const pasteUrlConstant = "pasteUrl"
- const pasteUrlSectionConstant = "pasteUrlSection"
-
- function copyPaste(){
- var copyText = document.getElementById(pasteUrlConstant);
-
- copyText.select();
- copyText.setSelectionRange(0, 99999);
-
- navigator.clipboard.writeText(copyText.value);
- }
-
- function togglePasteType() {
- const passwordType = document.getElementById(pasteTypeConstant).value;
- const passwordField = document.getElementById(passwordFieldConstant);
- if (passwordType === passwordTypeConstant) {
- passwordField.style.display = 'block';
- } else {
- passwordField.style.display = 'none';
- }
- }
-
- async function deriveKey(password, salt) {
- let encodedPassword = new TextEncoder().encode(password);
- let baseKey = await window.crypto.subtle.importKey(
- "raw",
- encodedPassword,
- { name: "PBKDF2" },
- false,
- ["deriveKey"],
- );
-
- let derivedKey = await window.crypto.subtle.deriveKey(
- {
- name: "PBKDF2",
- salt: salt,
- iterations: 600000,
- hash: "SHA-256",
- },
- baseKey,
- { name: "AES-GCM", length: 256 },
- true,
- ["encrypt", "decrypt"],
- );
-
- return derivedKey;
- }
-
- async function encryptData(data, password) {
- let salt = window.crypto.getRandomValues(new Uint8Array(16));
- let iv = window.crypto.getRandomValues(new Uint8Array(12));
- let key = await deriveKey(password, salt);
- let encodedData = new TextEncoder().encode(data);
-
- let encryptedContent = await window.crypto.subtle.encrypt(
- {
- name: "AES-GCM",
- iv: iv,
- tagLength: 128,
- },
- key,
- encodedData,
- );
-
- let ciphertext = encryptedContent.slice(
- 0,
- encryptedContent.byteLength - 16,
- );
- let authTag = encryptedContent.slice(encryptedContent.byteLength - 16);
-
- return {
- ciphertext: new Uint8Array(ciphertext),
- iv: iv,
- authTag: new Uint8Array(authTag),
- salt: salt,
- };
- }
-
- async function handlePaste() {
- let type = document.getElementById(pasteTypeConstant).value;
- let title = document.getElementById(titleConstant).value;
- let expiration = document.getElementById(expirationConstant).value;
- let plaintext = document.getElementById(plaintextConstant).value;
- let password = document.getElementById(passwordTypeConstant).value;
-
- let pasted_text = ""
- if (type == plainTypeConstant) {
- if (!plaintext || !title) return alert("Enter title and paste.");
-
- pasted_text = plaintext
- }
- else {
- if (!plaintext || !password || !title) return alert("Enter title, paste and password.");
-
- let encrypted = await encryptData(plaintext, password);
- pasted_text = btoa(JSON.stringify(encrypted))
- }
-
- let currentPath = window.location.origin;
-
- try {
- let response = await fetch(currentPath + "/submit", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- type: type,
- title: title,
- expiration: expiration,
- pasted_text: pasted_text
- }),
- });
-
- if (response.ok) {
- let jsonResponse = await response.json();
- document.getElementById(pasteUrlConstant).value = currentPath + "/get?id=" + jsonResponse.id;
- document.getElementById(pasteUrlSectionConstant).style.display = 'block';
- } else {
- console.error("Failed to submit data. Status:", response.status);
- }
- } catch (error) {
- console.error("Error making the POST request:", error);
- }
- }
- </script>
+ <script src="/script.js"></script>
</body>
</html> \ No newline at end of file
diff --git a/paste-cgi.py b/paste-cgi.py
index 88bd802..dd1d9e1 100644
--- a/paste-cgi.py
+++ b/paste-cgi.py
@@ -46,13 +46,33 @@ def check_working_dir(allowed_dir):
sys.exit(0)
+def status_405():
+ print("Status: 405 Method Not Allowed")
+ print("Content-Type: text/plain")
+ print("")
+ print("405 Method Not Allowed")
+ sys.exit(0)
+
+
+def status_415():
+ print("Status: 415 Unsupported Media Type")
+ print("Content-Type: text/plain")
+ print("")
+ print("415 Unsupported Media Type.")
+ sys.exit(0)
+
+
+def status_404():
+ print("Status: 404 Not Found")
+ print("Content-Type: text/plain")
+ print("")
+ print("File not found")
+ sys.exit(0)
+
+
def check_method(method):
if method not in ["GET", "POST"]:
- print("Status: 405 Method Not Allowed")
- print("Content-Type: text/plain")
- print("")
- print("405 Method Not Allowed")
- sys.exit(0)
+ status_405()
def get_content_lenght():
@@ -82,59 +102,7 @@ def validate_payload(payload):
):
if SubmitConstants.PASTED_TEXT in payload:
return
- print("Status: 415 Unsupported Media Type")
- print("Content-Type: text/plain")
- print("")
- print("415 Unsupported Media Type: Expected 'application/json'.")
- sys.exit(0)
-
-
-def return_index_html():
- try:
- with open("index.html", "r") as file:
- index_html = file.read()
-
- print("Content-Type: text/html")
- print("")
- print(index_html)
-
- except Exception:
- print("Status: 404 Not Found")
- print("Content-Type: text/html")
- print("")
- print("<html><body><h1>404 Not Found</h1></body></html>")
-
-
-def return_pico_css():
- try:
- with open("pico.min.css", "r") as file:
- pico_css = file.read()
-
- print("Content-Type: text/css")
- print("")
- print(pico_css)
-
- except Exception:
- print("Status: 404 Not Found")
- print("Content-Type: text/html")
- print("")
- print("<html><body><h1>404 Not Found</h1></body></html>")
-
-
-def return_favicon_svg():
- try:
- with open("favicon.svg", "r") as file:
- favicon_svg = file.read()
-
- print("Content-Type: image/svg+xml")
- print("")
- print(favicon_svg)
-
- except Exception:
- print("Status: 404 Not Found")
- print("Content-Type: text/html")
- print("")
- print("<html><body><h1>404 Not Found</h1></body></html>")
+ status_415()
def submit(post_data):
@@ -203,17 +171,11 @@ def handle_data(data):
def return_paste(query_string):
if query_string is None or not query_string.startswith("id="):
- print("Content-Type: text/plain")
- print("")
- print(query_string)
- sys.exit(0)
+ status_415()
id = query_string.split("=")
if len(id) != 2:
- print("Content-Type: text/plain")
- print("")
- print(query_string)
- sys.exit(0)
+ status_415()
id = id[1]
@@ -222,18 +184,10 @@ def return_paste(query_string):
os.chdir(directory)
if os.getcwd() != DATABASE_DIRECTORY:
- print("Content-Type: text/plain")
- print("")
- print("NICE TRY")
- sys.exit(0)
+ status_415()
if not os.path.exists(full_path):
- print("Status: 404 Not Found")
- print("Content-Type: text/plain")
- print("")
- print("File not found")
- print(full_path)
- sys.exit(0)
+ status_404()
with open(full_path, "r") as file:
data = json.load(file)
@@ -243,10 +197,7 @@ def return_paste(query_string):
if deleted:
os.remove(full_path)
- print("Status: 404 Not Found")
- print("Content-Type: text/plain")
- print("")
- print("File not found")
+ status_404()
print("Content-Type: application/json")
print("")
@@ -257,26 +208,7 @@ def return_paste(query_string):
sys.exit(0)
except Exception:
- print("Status: 404 Not Found")
- print("Content-Type: text/plain")
- print("")
- print("File not found")
-
-
-def return_get():
- try:
- with open("get.html", "r") as file:
- get_html = file.read()
-
- print("Content-Type: text/html")
- print("")
- print(get_html)
-
- except Exception:
- print("Status: 404 Not Found")
- print("Content-Type: text/html")
- print("")
- print("<html><body><h1>404 Not Found</h1></body></html>")
+ status_404()
allowed_dir = os.environ.get("ALLOWED_DIR", None)
@@ -293,15 +225,7 @@ content_length = get_content_lenght()
if method == "GET":
- if script_name == "/":
- return_index_html()
- elif script_name == "/pico.min.css":
- return_pico_css()
- elif script_name == "/favicon.svg":
- return_favicon_svg()
- elif script_name == "/get":
- return_get()
- elif script_name == "/paste":
+ if script_name == "/paste":
return_paste(query_string)
elif method == "POST":
post_data = sys.stdin.read(content_length)
diff --git a/script.js b/script.js
new file mode 100644
index 0000000..995156f
--- /dev/null
+++ b/script.js
@@ -0,0 +1,230 @@
+const pasteTypeConstant = "pasteType";
+const passwordFieldConstant = "passwordField";
+const passwordTypeConstant = "password";
+const plainTypeConstant = "plain";
+const titleConstant = "title";
+const expirationConstant = "expiration";
+const plaintextConstant = "plaintext";
+const pasteUrlConstant = "pasteUrl";
+const pasteUrlSectionConstant = "pasteUrlSection";
+const decryptedOutputConstant = "decryptedOutput";
+const decryptButtonConstant = "decryptBtn";
+
+let globalData;
+
+function copyPaste() {
+ const copyText = document.getElementById(
+ document.getElementById(pasteUrlConstant)
+ ? pasteUrlConstant
+ : decryptedOutputConstant
+ );
+ copyText.select();
+ copyText.setSelectionRange(0, 99999);
+ navigator.clipboard.writeText(copyText.value);
+}
+
+function togglePasteType() {
+ const passwordType = document.getElementById(pasteTypeConstant)?.value || globalData?.type;
+ const passwordField = document.getElementById(passwordFieldConstant);
+ const decryptButton = document.getElementById(decryptButtonConstant);
+
+ if (document.getElementById(titleConstant) && globalData?.title) {
+ document.getElementById(titleConstant).textContent = "Title:" + globalData.title;
+ }
+
+ if (passwordType === passwordTypeConstant) {
+ passwordField.style.display = "block";
+ if (decryptButton) decryptButton.style.display = "block";
+ } else {
+ passwordField.style.display = "none";
+ if (decryptButton) decryptButton.style.display = "none";
+ if (document.getElementById(decryptedOutputConstant)) {
+ document.getElementById(decryptedOutputConstant).textContent = globalData?.pasted_text || "";
+ }
+ document.getElementById(pasteUrlSectionConstant).style.display = "block";
+ }
+}
+
+async function deriveKey(password, salt) {
+ const encodedPassword = new TextEncoder().encode(password);
+ const baseKey = await window.crypto.subtle.importKey(
+ "raw",
+ encodedPassword,
+ { name: "PBKDF2" },
+ false,
+ ["deriveKey"]
+ );
+
+ const derivedKey = await window.crypto.subtle.deriveKey(
+ {
+ name: "PBKDF2",
+ salt: salt,
+ iterations: 600000,
+ hash: "SHA-256",
+ },
+ baseKey,
+ { name: "AES-GCM", length: 256 },
+ true,
+ ["encrypt", "decrypt"]
+ );
+
+ return derivedKey;
+}
+
+async function encryptData(data, password) {
+ const salt = window.crypto.getRandomValues(new Uint8Array(16));
+ const iv = window.crypto.getRandomValues(new Uint8Array(12));
+ const key = await deriveKey(password, salt);
+ const encodedData = new TextEncoder().encode(data);
+
+ const encryptedContent = await window.crypto.subtle.encrypt(
+ {
+ name: "AES-GCM",
+ iv: iv,
+ tagLength: 128,
+ },
+ key,
+ encodedData
+ );
+
+ const ciphertext = encryptedContent.slice(0, encryptedContent.byteLength - 16);
+ const authTag = encryptedContent.slice(encryptedContent.byteLength - 16);
+
+ return {
+ ciphertext: new Uint8Array(ciphertext),
+ iv: iv,
+ authTag: new Uint8Array(authTag),
+ salt: salt,
+ };
+}
+
+async function decryptData(encryptedData, password) {
+ const { ciphertext, iv, authTag, salt } = encryptedData;
+ const key = await deriveKey(password, salt);
+
+ const dataWithAuthTag = new Uint8Array(ciphertext.length + authTag.length);
+ dataWithAuthTag.set(ciphertext, 0);
+ dataWithAuthTag.set(authTag, ciphertext.length);
+
+ const decryptedContent = await window.crypto.subtle.decrypt(
+ { name: "AES-GCM", iv: iv, tagLength: 128 },
+ key,
+ dataWithAuthTag
+ );
+
+ return new TextDecoder().decode(decryptedContent);
+}
+
+async function handlePaste() {
+ const type = document.getElementById(pasteTypeConstant).value;
+ const title = document.getElementById(titleConstant).value;
+ const expiration = document.getElementById(expirationConstant).value;
+ const plaintext = document.getElementById(plaintextConstant).value;
+ const password = document.getElementById(passwordTypeConstant).value;
+
+ let pasted_text = "";
+
+ if (type === plainTypeConstant) {
+ if (!plaintext || !title) return alert("Enter title and paste.");
+ pasted_text = plaintext;
+ } else {
+ if (!plaintext || !password || !title) return alert("Enter title, paste and password.");
+ const encrypted = await encryptData(plaintext, password);
+ pasted_text = btoa(JSON.stringify(encrypted));
+ }
+
+ const currentPath = window.location.origin;
+
+ try {
+ const response = await fetch(currentPath + "/submit", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ type: type,
+ title: title,
+ expiration: expiration,
+ pasted_text: pasted_text,
+ }),
+ });
+
+ if (response.ok) {
+ const jsonResponse = await response.json();
+ document.getElementById(pasteUrlConstant).value = currentPath + "/get?id=" + jsonResponse.id;
+ document.getElementById(pasteUrlSectionConstant).style.display = "block";
+ } else {
+ console.error("Failed to submit data. Status:", response.status);
+ }
+ } catch (error) {
+ console.error("Error making the POST request:", error);
+ }
+}
+
+async function handleDecrypt() {
+ const base64 = globalData.pasted_text;
+ const password = document.getElementById(passwordTypeConstant).value;
+ if (!base64 || !password) return alert("Enter encrypted text and password.");
+
+ try {
+ const parsed = JSON.parse(atob(base64));
+ const encryptedData = {
+ salt: new Uint8Array(Object.values(parsed.salt)),
+ iv: new Uint8Array(Object.values(parsed.iv)),
+ authTag: new Uint8Array(Object.values(parsed.authTag)),
+ ciphertext: new Uint8Array(Object.values(parsed.ciphertext)),
+ };
+
+ const decrypted = await decryptData(encryptedData, password);
+ document.getElementById(decryptedOutputConstant).value = decrypted;
+ document.getElementById(pasteUrlSectionConstant).style.display = "block";
+ } catch (err) {
+ document.getElementById(decryptedOutputConstant).value = "❌ Decryption failed.";
+ }
+}
+
+async function loadPasteFromUrl() {
+ const urlParams = new URLSearchParams(window.location.search);
+ const id = urlParams.get("id");
+
+ if (!id) return;
+
+ const fetchUrl = `${window.location.origin}/paste?id=${id}`;
+
+ try {
+ const response = await fetch(fetchUrl);
+ if (!response.ok) throw new Error("Failed to fetch data");
+
+ globalData = await response.json();
+ togglePasteType();
+ } catch (error) {
+ alert("No File Found");
+ console.error("Error fetching data:", error);
+ }
+}
+
+window.onload = async function () {
+ let path = window.location.pathname;
+ if (path != "/get")
+ return;
+ const urlParams = new URLSearchParams(window.location.search);
+ const id = urlParams.get('id');
+
+ const fetchUrl = `${window.location.origin}/paste?id=${id}`;
+
+ try {
+ const response = await fetch(fetchUrl);
+
+ if (!response.ok) {
+ throw new Error('Failed to fetch data');
+ }
+
+ const data = await response.json();
+
+ globalData = data;
+ togglePasteType()
+ } catch (error) {
+ alert("No File Found")
+ console.error('Error fetching data:', error);
+ }
+}; \ No newline at end of file