Use encrypt image names in html
This commit is contained in:
parent
81b7339634
commit
0480943093
94
Cargo.lock
generated
94
Cargo.lock
generated
@ -2,6 +2,16 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aead"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alloc-no-stdlib"
|
name = "alloc-no-stdlib"
|
||||||
version = "2.0.4"
|
version = "2.0.4"
|
||||||
@ -342,6 +352,30 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chacha20"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7fc89c7c5b9e7a02dfe45cd2367bae382f9ed31c61ca8debe5f827c420a2f08"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"cipher",
|
||||||
|
"cpufeatures",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chacha20poly1305"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
|
||||||
|
dependencies = [
|
||||||
|
"aead",
|
||||||
|
"chacha20",
|
||||||
|
"cipher",
|
||||||
|
"poly1305",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.23"
|
version = "0.4.23"
|
||||||
@ -355,6 +389,17 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cipher"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"inout",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codespan-reporting"
|
name = "codespan-reporting"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
@ -415,6 +460,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
|
"rand_core",
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -673,6 +719,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hmac"
|
name = "hmac"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@ -802,10 +857,11 @@ dependencies = [
|
|||||||
"axum",
|
"axum",
|
||||||
"axum-sessions",
|
"axum-sessions",
|
||||||
"axum-template",
|
"axum-template",
|
||||||
|
"chacha20poly1305",
|
||||||
|
"hex",
|
||||||
"image",
|
"image",
|
||||||
"kamadak-exif",
|
"kamadak-exif",
|
||||||
"minijinja",
|
"minijinja",
|
||||||
"rand",
|
|
||||||
"serde",
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
@ -813,6 +869,15 @@ dependencies = [
|
|||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inout"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
@ -1094,6 +1159,17 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "poly1305"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
|
||||||
|
dependencies = [
|
||||||
|
"cpufeatures",
|
||||||
|
"opaque-debug",
|
||||||
|
"universal-hash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@ -1619,6 +1695,16 @@ version = "0.1.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "universal-hash"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -1821,3 +1907,9 @@ name = "windows_x86_64_msvc"
|
|||||||
version = "0.42.1"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize"
|
||||||
|
version = "1.5.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
|
||||||
|
@ -8,10 +8,11 @@ anyhow = "1.0.69"
|
|||||||
axum = { version = "0.6.7", features = ["form", "macros"] }
|
axum = { version = "0.6.7", features = ["form", "macros"] }
|
||||||
axum-sessions = "0.4.1"
|
axum-sessions = "0.4.1"
|
||||||
axum-template = { version = "0.14.0", features = ["minijinja"] }
|
axum-template = { version = "0.14.0", features = ["minijinja"] }
|
||||||
|
chacha20poly1305 = { version = "0.10.1", features = ["std"] }
|
||||||
|
hex = { version = "0.4.3", features = ["serde"] }
|
||||||
image = { version = "0.24.5", default-features = false, features = ["jpeg", "webp-encoder"] }
|
image = { version = "0.24.5", default-features = false, features = ["jpeg", "webp-encoder"] }
|
||||||
kamadak-exif = "0.5.5"
|
kamadak-exif = "0.5.5"
|
||||||
minijinja = "0.30.4"
|
minijinja = "0.30.4"
|
||||||
rand = { version = "0.8.5", features = ["min_const_gen"] }
|
|
||||||
serde = { version = "1.0.152", features = ["derive"] }
|
serde = { version = "1.0.152", features = ["derive"] }
|
||||||
tokio = { version = "1.25.0", features = ["full"] }
|
tokio = { version = "1.25.0", features = ["full"] }
|
||||||
tower-http = { version = "0.3.5", features = ["fs", "trace", "compression-br"] }
|
tower-http = { version = "0.3.5", features = ["fs", "trace", "compression-br"] }
|
||||||
|
54
src/main.rs
54
src/main.rs
@ -1,13 +1,13 @@
|
|||||||
use std::{net::SocketAddr, path::{Path, PathBuf}, ffi::OsStr, fs::File, io::{BufReader, Seek, Cursor, BufRead}, time::SystemTime, env::args_os};
|
use std::{net::SocketAddr, path::{Path, PathBuf}, ffi::{OsStr, OsString}, fs::File, io::{BufReader, Seek, Cursor, BufRead}, time::SystemTime, env::args_os, os::unix::prelude::OsStrExt};
|
||||||
|
|
||||||
use anyhow::{Context, Result, anyhow};
|
use anyhow::{Context, Result, anyhow};
|
||||||
use axum::{Router, routing::{get, post}, response::{IntoResponse, Redirect}, http::{StatusCode, header}, extract::{self, State}, Form, handler::Handler};
|
use axum::{Router, routing::{get, post}, response::{IntoResponse, Redirect}, http::{StatusCode, header}, extract::{self, State}, Form, handler::Handler};
|
||||||
use axum_sessions::{async_session::CookieStore, SessionLayer, extractors::{ReadableSession, WritableSession}};
|
use axum_sessions::{async_session::CookieStore, SessionLayer, extractors::{ReadableSession, WritableSession}};
|
||||||
use axum_template::{RenderHtml, engine::Engine};
|
use axum_template::{RenderHtml, engine::Engine};
|
||||||
|
use chacha20poly1305::{XChaCha20Poly1305, KeyInit, AeadCore, aead::{OsRng, rand_core::RngCore, Aead}, XNonce};
|
||||||
use error::AppError;
|
use error::AppError;
|
||||||
use exif::{Tag, Value, Field, In, Exif};
|
use exif::{Tag, Value, Field, In, Exif};
|
||||||
use image::{imageops::FilterType, DynamicImage};
|
use image::{imageops::FilterType, DynamicImage};
|
||||||
use rand::Rng;
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use tokio::task;
|
use tokio::task;
|
||||||
use tower_http::{trace::{self, TraceLayer}, compression::CompressionLayer};
|
use tower_http::{trace::{self, TraceLayer}, compression::CompressionLayer};
|
||||||
@ -25,17 +25,22 @@ mod error;
|
|||||||
|
|
||||||
type TemplateEngine = Engine<minijinja::Environment<'static>>;
|
type TemplateEngine = Engine<minijinja::Environment<'static>>;
|
||||||
type ImageDir = PathBuf;
|
type ImageDir = PathBuf;
|
||||||
|
type SecretKey = [u8; 64];
|
||||||
|
|
||||||
#[derive(Clone, extract::FromRef)]
|
#[derive(Clone, extract::FromRef)]
|
||||||
struct ApplicationState {
|
struct ApplicationState {
|
||||||
engine: TemplateEngine,
|
engine: TemplateEngine,
|
||||||
image_dir: ImageDir,
|
image_dir: ImageDir,
|
||||||
|
secret_key: SecretKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let image_path = args_os().nth(1).expect("Usage: image-gallery IMAGE_DIRECTORY");
|
let image_path = args_os().nth(1).expect("Usage: image-gallery IMAGE_DIRECTORY");
|
||||||
|
|
||||||
|
let mut secret_key: SecretKey = [0; 64];
|
||||||
|
OsRng.fill_bytes(&mut secret_key);
|
||||||
|
|
||||||
let default_tracing = "image_gallery=debug,tower_http=info".into();
|
let default_tracing = "image_gallery=debug,tower_http=info".into();
|
||||||
let tracing_filter = EnvFilter::try_from_default_env().unwrap_or(default_tracing);
|
let tracing_filter = EnvFilter::try_from_default_env().unwrap_or(default_tracing);
|
||||||
let tracing_formatter = tracing_subscriber::fmt::layer()
|
let tracing_formatter = tracing_subscriber::fmt::layer()
|
||||||
@ -51,15 +56,14 @@ async fn main() {
|
|||||||
jinja.add_template("index", include_str!("../templates/index.html")).unwrap();
|
jinja.add_template("index", include_str!("../templates/index.html")).unwrap();
|
||||||
jinja.add_template("login", include_str!("../templates/login.html")).unwrap();
|
jinja.add_template("login", include_str!("../templates/login.html")).unwrap();
|
||||||
|
|
||||||
let secret = rand::thread_rng().gen::<[u8; 128]>();
|
let session_layer = SessionLayer::new(CookieStore::new(), &secret_key)
|
||||||
let session_layer = SessionLayer::new(CookieStore::new(), &secret)
|
|
||||||
.with_cookie_name("session")
|
.with_cookie_name("session")
|
||||||
.with_session_ttl(None);
|
.with_session_ttl(None);
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(index.layer(CompressionLayer::new())))
|
.route("/", get(index.layer(CompressionLayer::new())))
|
||||||
.route("/authenticate", post(authenticate))
|
.route("/authenticate", post(authenticate))
|
||||||
.route("/image/:image", get(converted_image))
|
.route("/image/:encrypted_filename_hex", get(converted_image))
|
||||||
.layer(TraceLayer::new_for_http()
|
.layer(TraceLayer::new_for_http()
|
||||||
.make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
|
.make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
|
||||||
.on_response(trace::DefaultOnResponse::new().level(Level::INFO))
|
.on_response(trace::DefaultOnResponse::new().level(Level::INFO))
|
||||||
@ -68,6 +72,7 @@ async fn main() {
|
|||||||
.with_state(ApplicationState {
|
.with_state(ApplicationState {
|
||||||
engine: Engine::from(jinja),
|
engine: Engine::from(jinja),
|
||||||
image_dir: PathBuf::from(image_path),
|
image_dir: PathBuf::from(image_path),
|
||||||
|
secret_key,
|
||||||
});
|
});
|
||||||
|
|
||||||
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
|
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
|
||||||
@ -78,11 +83,14 @@ async fn main() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize, Default)]
|
||||||
pub struct ImageInfo {
|
pub struct ImageInfo {
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
name: String,
|
#[serde(with = "hex")]
|
||||||
|
encrypted_name: Vec<u8>,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
name: OsString,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
@ -94,6 +102,7 @@ pub struct IndexTempalte {
|
|||||||
async fn index(
|
async fn index(
|
||||||
engine: TemplateEngine,
|
engine: TemplateEngine,
|
||||||
State(image_dir): State<ImageDir>,
|
State(image_dir): State<ImageDir>,
|
||||||
|
State(secret_key): State<SecretKey>,
|
||||||
session: ReadableSession,
|
session: ReadableSession,
|
||||||
) -> Result<impl IntoResponse, AppError> {
|
) -> Result<impl IntoResponse, AppError> {
|
||||||
let logged_in = session.get::<()>("logged_in").is_some();
|
let logged_in = session.get::<()>("logged_in").is_some();
|
||||||
@ -101,6 +110,19 @@ async fn index(
|
|||||||
if logged_in {
|
if logged_in {
|
||||||
let images = read_images(&image_dir)?;
|
let images = read_images(&image_dir)?;
|
||||||
|
|
||||||
|
// Encrypt image names
|
||||||
|
let chacha_key = chacha20poly1305::Key::from_slice(&secret_key[0..32]);
|
||||||
|
let chacha = XChaCha20Poly1305::new(chacha_key);
|
||||||
|
let images = images.into_iter().map(|mut image_info| {
|
||||||
|
let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
|
||||||
|
let mut ciphertext = chacha.encrypt(&nonce, image_info.name.as_bytes())?;
|
||||||
|
|
||||||
|
image_info.encrypted_name = nonce.as_slice().to_vec();
|
||||||
|
image_info.encrypted_name.append(&mut ciphertext);
|
||||||
|
|
||||||
|
Ok(image_info)
|
||||||
|
}).collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
let images = images.into_iter().rev().collect();
|
let images = images.into_iter().rev().collect();
|
||||||
|
|
||||||
Ok(RenderHtml("index", engine, IndexTempalte {
|
Ok(RenderHtml("index", engine, IndexTempalte {
|
||||||
@ -131,15 +153,26 @@ async fn authenticate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn converted_image(
|
async fn converted_image(
|
||||||
extract::Path(image): extract::Path<String>,
|
extract::Path(encrypted_filename_hex): extract::Path<String>,
|
||||||
State(image_dir): State<ImageDir>,
|
State(image_dir): State<ImageDir>,
|
||||||
|
State(secret_key): State<SecretKey>,
|
||||||
session: ReadableSession,
|
session: ReadableSession,
|
||||||
) -> Result<impl IntoResponse, AppError> {
|
) -> Result<impl IntoResponse, AppError> {
|
||||||
session.get::<()>("logged_in")
|
session.get::<()>("logged_in")
|
||||||
.ok_or(anyhow!("Trying to load image while not logged in!"))
|
.ok_or(anyhow!("Trying to load image while not logged in!"))
|
||||||
.context(StatusCode::FORBIDDEN)?;
|
.context(StatusCode::FORBIDDEN)?;
|
||||||
|
|
||||||
let image_path = image_dir.join(image);
|
// Decrypt image name
|
||||||
|
let image_name = {
|
||||||
|
let encrypted_filename = hex::decode(&encrypted_filename_hex)?;
|
||||||
|
let nonce = XNonce::from_slice(&encrypted_filename[0..24]);
|
||||||
|
let ciphertext = &encrypted_filename[24..];
|
||||||
|
let chacha_key = chacha20poly1305::Key::from_slice(&secret_key[0..32]);
|
||||||
|
let chacha = XChaCha20Poly1305::new(chacha_key);
|
||||||
|
chacha.decrypt(nonce, ciphertext)
|
||||||
|
}.with_context(|| format!("Could not decrypt filename {}", encrypted_filename_hex))?;
|
||||||
|
|
||||||
|
let image_path = image_dir.join(OsStr::from_bytes(&image_name));
|
||||||
|
|
||||||
image_path.exists()
|
image_path.exists()
|
||||||
.then_some(())
|
.then_some(())
|
||||||
@ -279,7 +312,8 @@ fn read_image_info(path: &Path) -> Result<ImageInfo> {
|
|||||||
Ok(ImageInfo {
|
Ok(ImageInfo {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
name: path.file_name().expect("invalid file path").to_string_lossy().to_string(),
|
name: path.file_name().expect("invalid file path").to_owned(),
|
||||||
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
<main>
|
<main>
|
||||||
{% for image in images %}
|
{% for image in images %}
|
||||||
<div style="aspect-ratio: {{image.width}} / {{image.height}}; flex-grow: {{10*image.width/image.height}};">
|
<div style="aspect-ratio: {{image.width}} / {{image.height}}; flex-grow: {{10*image.width/image.height}};">
|
||||||
<img loading="lazy" src="/image/{{image.name}}" alt="YOU SHALL NOT SAVE THIS IMAGE!" />
|
<img loading="lazy" src="/image/{{image.encrypted_name}}" alt="YOU SHALL NOT SAVE THIS IMAGE!" />
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</main>
|
</main>
|
||||||
|
Loading…
Reference in New Issue
Block a user