clippy
This commit is contained in:
parent
24eff63a5e
commit
b833f97c55
7 changed files with 86 additions and 96 deletions
3
build.sh
3
build.sh
|
@ -12,9 +12,6 @@ sed -i 's#stylesheet" href="/main#stylesheet" href="/static/main#g' dist/index.h
|
||||||
# Build server
|
# Build server
|
||||||
cargo build --release --bin omegaupload-server
|
cargo build --release --bin omegaupload-server
|
||||||
|
|
||||||
# index.html no longer needed, served statically by the upload server
|
|
||||||
rm dist/index.html
|
|
||||||
|
|
||||||
# Prepare assets for upload to webserver
|
# Prepare assets for upload to webserver
|
||||||
mkdir -p dist/static
|
mkdir -p dist/static
|
||||||
# Move everything that's not index.html into a `static` subdir
|
# Move everything that's not index.html into a `static` subdir
|
||||||
|
|
|
@ -134,13 +134,14 @@ fn handle_download(mut url: ParsedUrl) -> Result<()> {
|
||||||
|
|
||||||
let mut data = res.bytes()?.as_ref().to_vec();
|
let mut data = res.bytes()?.as_ref().to_vec();
|
||||||
|
|
||||||
let mut password = None;
|
let password = if url.needs_password {
|
||||||
if url.needs_password {
|
|
||||||
// Only print prompt on interactive, else it messes with output
|
// Only print prompt on interactive, else it messes with output
|
||||||
let maybe_password =
|
let maybe_password =
|
||||||
prompt_password_stderr("Please enter the password to access this paste: ")?;
|
prompt_password_stderr("Please enter the password to access this paste: ")?;
|
||||||
password = Some(SecretVec::new(maybe_password.into_bytes()));
|
Some(SecretVec::new(maybe_password.into_bytes()))
|
||||||
}
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
open_in_place(&mut data, &url.decryption_key, password)?;
|
open_in_place(&mut data, &url.decryption_key, password)?;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use std::fmt::Display;
|
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use argon2::Argon2;
|
use argon2::Argon2;
|
||||||
|
@ -11,33 +10,16 @@ use rand::{thread_rng, Rng};
|
||||||
use secrecy::{ExposeSecret, Secret, SecretVec, Zeroize};
|
use secrecy::{ExposeSecret, Secret, SecretVec, Zeroize};
|
||||||
use typenum::Unsigned;
|
use typenum::Unsigned;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
ChaCha20Poly1305(chacha20poly1305::aead::Error),
|
#[error("Invalid password.")]
|
||||||
Argon2(argon2::Error),
|
Password,
|
||||||
}
|
#[error("Invalid secret key.")]
|
||||||
|
SecretKey,
|
||||||
impl From<chacha20poly1305::aead::Error> for Error {
|
#[error("An error occurred while trying to decrypt the blob.")]
|
||||||
fn from(err: chacha20poly1305::aead::Error) -> Self {
|
Encryption,
|
||||||
Error::ChaCha20Poly1305(err)
|
#[error("An error occurred while trying to derive a secret key.")]
|
||||||
}
|
Kdf,
|
||||||
}
|
|
||||||
|
|
||||||
impl From<argon2::Error> for Error {
|
|
||||||
fn from(err: argon2::Error) -> Self {
|
|
||||||
Error::Argon2(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for Error {}
|
|
||||||
|
|
||||||
impl Display for Error {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Error::ChaCha20Poly1305(_) => write!(f, "Decryption failed"),
|
|
||||||
Error::Argon2(_) => write!(f, "KDF failed"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This struct intentionally prevents implement Clone or Copy
|
// This struct intentionally prevents implement Clone or Copy
|
||||||
|
@ -72,7 +54,7 @@ impl DerefMut for Key {
|
||||||
|
|
||||||
impl Zeroize for Key {
|
impl Zeroize for Key {
|
||||||
fn zeroize(&mut self) {
|
fn zeroize(&mut self) {
|
||||||
self.0.zeroize()
|
self.0.zeroize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +74,7 @@ impl Zeroize for Key {
|
||||||
///
|
///
|
||||||
/// Where:
|
/// Where:
|
||||||
/// - `C(message, key, nonce)` represents encrypting a provided message with
|
/// - `C(message, key, nonce)` represents encrypting a provided message with
|
||||||
/// XChaCha20Poly1305.
|
/// `XChaCha20Poly1305`.
|
||||||
/// - `rng_key` represents a randomly generated key.
|
/// - `rng_key` represents a randomly generated key.
|
||||||
/// - `kdf(pw, salt)` represents a key derived from Argon2.
|
/// - `kdf(pw, salt)` represents a key derived from Argon2.
|
||||||
pub fn seal_in_place(
|
pub fn seal_in_place(
|
||||||
|
@ -101,14 +83,18 @@ pub fn seal_in_place(
|
||||||
) -> Result<Secret<Key>, Error> {
|
) -> Result<Secret<Key>, Error> {
|
||||||
let (key, nonce) = gen_key_nonce();
|
let (key, nonce) = gen_key_nonce();
|
||||||
let cipher = XChaCha20Poly1305::new(key.expose_secret());
|
let cipher = XChaCha20Poly1305::new(key.expose_secret());
|
||||||
cipher.encrypt_in_place(&nonce, &[], message)?;
|
cipher
|
||||||
|
.encrypt_in_place(&nonce, &[], message)
|
||||||
|
.map_err(|_| Error::Encryption)?;
|
||||||
|
|
||||||
let mut maybe_salt_string = None;
|
let mut maybe_salt_string = None;
|
||||||
if let Some(password) = pw {
|
if let Some(password) = pw {
|
||||||
let (key, salt_string) = kdf(&password)?;
|
let (key, salt_string) = kdf(&password).map_err(|_| Error::Kdf)?;
|
||||||
maybe_salt_string = Some(salt_string);
|
maybe_salt_string = Some(salt_string);
|
||||||
let cipher = XChaCha20Poly1305::new(key.expose_secret());
|
let cipher = XChaCha20Poly1305::new(key.expose_secret());
|
||||||
cipher.encrypt_in_place(&nonce.increment(), &[], message)?;
|
cipher
|
||||||
|
.encrypt_in_place(&nonce.increment(), &[], message)
|
||||||
|
.map_err(|_| Error::Encryption)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
message.extend_from_slice(nonce.as_slice());
|
message.extend_from_slice(nonce.as_slice());
|
||||||
|
@ -127,7 +113,9 @@ pub fn open_in_place(
|
||||||
let salt_buf = data.split_off(data.len() - Salt::SIZE);
|
let salt_buf = data.split_off(data.len() - Salt::SIZE);
|
||||||
let argon = Argon2::default();
|
let argon = Argon2::default();
|
||||||
let mut pw_key = Key::default();
|
let mut pw_key = Key::default();
|
||||||
argon.hash_password_into(password.expose_secret(), &salt_buf, &mut pw_key)?;
|
argon
|
||||||
|
.hash_password_into(password.expose_secret(), &salt_buf, &mut pw_key)
|
||||||
|
.map_err(|_| Error::Kdf)?;
|
||||||
Some(Secret::new(pw_key))
|
Some(Secret::new(pw_key))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -139,11 +127,15 @@ pub fn open_in_place(
|
||||||
|
|
||||||
if let Some(key) = pw_key {
|
if let Some(key) = pw_key {
|
||||||
let cipher = XChaCha20Poly1305::new(key.expose_secret());
|
let cipher = XChaCha20Poly1305::new(key.expose_secret());
|
||||||
cipher.decrypt_in_place(&nonce.increment(), &[], data)?;
|
cipher
|
||||||
|
.decrypt_in_place(&nonce.increment(), &[], data)
|
||||||
|
.map_err(|_| Error::Password)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cipher = XChaCha20Poly1305::new(key.expose_secret());
|
let cipher = XChaCha20Poly1305::new(key.expose_secret());
|
||||||
cipher.decrypt_in_place(&nonce, &[], data)?;
|
cipher
|
||||||
|
.decrypt_in_place(&nonce, &[], data)
|
||||||
|
.map_err(|_| Error::SecretKey)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -208,13 +200,13 @@ impl Nonce {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
struct Salt([u8; Salt::SIZE]);
|
struct Salt([u8; Self::SIZE]);
|
||||||
|
|
||||||
impl Salt {
|
impl Salt {
|
||||||
const SIZE: usize = argon2::password_hash::Salt::RECOMMENDED_LENGTH;
|
const SIZE: usize = argon2::password_hash::Salt::RECOMMENDED_LENGTH;
|
||||||
|
|
||||||
fn random() -> Self {
|
fn random() -> Self {
|
||||||
let mut salt = [0u8; Salt::SIZE];
|
let mut salt = [0_u8; Self::SIZE];
|
||||||
thread_rng().fill(&mut salt);
|
thread_rng().fill(&mut salt);
|
||||||
Self(salt)
|
Self(salt)
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,9 +41,7 @@ impl From<&str> for PartialParsedUrl {
|
||||||
// Base64 has an interesting property that the length of an encoded text
|
// Base64 has an interesting property that the length of an encoded text
|
||||||
// is always 4/3rds larger than the original data.
|
// is always 4/3rds larger than the original data.
|
||||||
if !fragment.contains("key") {
|
if !fragment.contains("key") {
|
||||||
let decryption_key = base64::decode(fragment)
|
let decryption_key = base64::decode(fragment).ok().and_then(Key::new_secret);
|
||||||
.ok()
|
|
||||||
.and_then(|k| Key::new_secret(k));
|
|
||||||
|
|
||||||
return Self {
|
return Self {
|
||||||
decryption_key,
|
decryption_key,
|
||||||
|
@ -66,7 +64,7 @@ impl From<&str> for PartialParsedUrl {
|
||||||
for (key, value) in args {
|
for (key, value) in args {
|
||||||
match (key, value) {
|
match (key, value) {
|
||||||
("key", Some(value)) => {
|
("key", Some(value)) => {
|
||||||
decryption_key = base64::decode(value).ok().and_then(|k| Key::new_secret(k));
|
decryption_key = base64::decode(value).ok().and_then(Key::new_secret);
|
||||||
}
|
}
|
||||||
("pw", _) => {
|
("pw", _) => {
|
||||||
needs_password = true;
|
needs_password = true;
|
||||||
|
|
|
@ -94,6 +94,8 @@ async fn main() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See https://link.eddie.sh/5JHlD
|
||||||
|
#[allow(clippy::cognitive_complexity)]
|
||||||
fn set_up_expirations(db: &Arc<DB>) {
|
fn set_up_expirations(db: &Arc<DB>) {
|
||||||
let mut corrupted = 0;
|
let mut corrupted = 0;
|
||||||
let mut expired = 0;
|
let mut expired = 0;
|
||||||
|
@ -161,15 +163,12 @@ fn set_up_expirations(db: &Arc<DB>) {
|
||||||
|
|
||||||
async fn handle_signals(mut signals: Signals, db: Arc<DB>) {
|
async fn handle_signals(mut signals: Signals, db: Arc<DB>) {
|
||||||
while let Some(signal) = signals.next().await {
|
while let Some(signal) = signals.next().await {
|
||||||
match signal {
|
if signal == SIGUSR1 {
|
||||||
SIGUSR1 => {
|
let meta_cf = db.cf_handle(META_CF_NAME).unwrap();
|
||||||
let meta_cf = db.cf_handle(META_CF_NAME).unwrap();
|
info!(
|
||||||
info!(
|
"Active paste count: {}",
|
||||||
"Active paste count: {}",
|
db.iterator_cf(meta_cf, IteratorMode::Start).count()
|
||||||
db.iterator_cf(meta_cf, IteratorMode::Start).count()
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use gloo_console::log;
|
use gloo_console::log;
|
||||||
use js_sys::{Array, Uint8Array};
|
use js_sys::{Array, Uint8Array};
|
||||||
use omegaupload_common::crypto::{open_in_place, Key};
|
use omegaupload_common::crypto::{open_in_place, Error, Key};
|
||||||
use omegaupload_common::secrecy::{Secret, SecretVec};
|
use omegaupload_common::secrecy::{Secret, SecretVec};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
|
@ -36,11 +35,10 @@ fn now() -> f64 {
|
||||||
|
|
||||||
pub fn decrypt(
|
pub fn decrypt(
|
||||||
mut container: Vec<u8>,
|
mut container: Vec<u8>,
|
||||||
key: Secret<Key>,
|
key: &Secret<Key>,
|
||||||
maybe_password: Option<SecretVec<u8>>,
|
maybe_password: Option<SecretVec<u8>>,
|
||||||
) -> Result<DecryptedData, PasteCompleteConstructionError> {
|
) -> Result<DecryptedData, Error> {
|
||||||
open_in_place(&mut container, &key, maybe_password)
|
open_in_place(&mut container, key, maybe_password)?;
|
||||||
.map_err(|_| PasteCompleteConstructionError::Decryption)?;
|
|
||||||
|
|
||||||
let mime_type = tree_magic_mini::from_u8(&container);
|
let mime_type = tree_magic_mini::from_u8(&container);
|
||||||
log!("Mimetype: ", mime_type);
|
log!("Mimetype: ", mime_type);
|
||||||
|
@ -63,15 +61,22 @@ pub fn decrypt(
|
||||||
log!(format!("Blob conversion completed in {}ms", now() - start));
|
log!(format!("Blob conversion completed in {}ms", now() - start));
|
||||||
|
|
||||||
if mime_type.starts_with("text/") {
|
if mime_type.starts_with("text/") {
|
||||||
String::from_utf8(container)
|
if let Ok(string) = String::from_utf8(container) {
|
||||||
.map(Arc::new)
|
Ok(DecryptedData::String(Arc::new(string)))
|
||||||
.map(DecryptedData::String)
|
} else {
|
||||||
.map_err(|_| PasteCompleteConstructionError::InvalidEncoding)
|
Ok(DecryptedData::Blob(blob))
|
||||||
} else if mime_type.starts_with("image/") || mime_type == "application/x-riff" {
|
}
|
||||||
|
} else if mime_type.starts_with("image/")
|
||||||
|
// application/x-riff is WebP
|
||||||
|
|| mime_type == "application/x-riff"
|
||||||
|
{
|
||||||
Ok(DecryptedData::Image(blob, container.len()))
|
Ok(DecryptedData::Image(blob, container.len()))
|
||||||
} else if mime_type.starts_with("audio/") {
|
} else if mime_type.starts_with("audio/") {
|
||||||
Ok(DecryptedData::Audio(blob))
|
Ok(DecryptedData::Audio(blob))
|
||||||
} else if mime_type.starts_with("video/") || mime_type == "application/x-matroska" {
|
} else if mime_type.starts_with("video/")
|
||||||
|
// application/x-matroska is mkv
|
||||||
|
|| mime_type == "application/x-matroska"
|
||||||
|
{
|
||||||
Ok(DecryptedData::Video(blob))
|
Ok(DecryptedData::Video(blob))
|
||||||
} else if mime_type == "application/zip" {
|
} else if mime_type == "application/zip" {
|
||||||
let mut entries = vec![];
|
let mut entries = vec![];
|
||||||
|
@ -103,25 +108,3 @@ pub fn decrypt(
|
||||||
Ok(DecryptedData::Blob(blob))
|
Ok(DecryptedData::Blob(blob))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum PasteCompleteConstructionError {
|
|
||||||
Decryption,
|
|
||||||
InvalidEncoding,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for PasteCompleteConstructionError {}
|
|
||||||
|
|
||||||
impl Display for PasteCompleteConstructionError {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
PasteCompleteConstructionError::Decryption => {
|
|
||||||
write!(f, "Failed to decrypt data.")
|
|
||||||
}
|
|
||||||
PasteCompleteConstructionError::InvalidEncoding => write!(
|
|
||||||
f,
|
|
||||||
"Got an file with a text/* mime type, but was unable to parsed as valid UTF-8?"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ use gloo_console::{error, log};
|
||||||
use http::uri::PathAndQuery;
|
use http::uri::PathAndQuery;
|
||||||
use http::{StatusCode, Uri};
|
use http::{StatusCode, Uri};
|
||||||
use js_sys::{Array, JsString, Object, Uint8Array};
|
use js_sys::{Array, JsString, Object, Uint8Array};
|
||||||
use omegaupload_common::crypto::Key;
|
use omegaupload_common::crypto::{Error as CryptoError, Key};
|
||||||
use omegaupload_common::secrecy::{Secret, SecretVec};
|
use omegaupload_common::secrecy::{Secret, SecretVec};
|
||||||
use omegaupload_common::{Expiration, PartialParsedUrl};
|
use omegaupload_common::{Expiration, PartialParsedUrl};
|
||||||
use reqwasm::http::Request;
|
use reqwasm::http::Request;
|
||||||
|
@ -89,10 +89,15 @@ fn main() {
|
||||||
loop {
|
loop {
|
||||||
let pw = window().prompt_with_message("A password is required to decrypt this paste:");
|
let pw = window().prompt_with_message("A password is required to decrypt this paste:");
|
||||||
|
|
||||||
if let Ok(Some(password)) = pw {
|
match pw {
|
||||||
if !password.is_empty() {
|
Ok(Some(password)) if password.is_empty() => {
|
||||||
break Some(SecretVec::new(password.into_bytes()));
|
break Some(SecretVec::new(password.into_bytes()));
|
||||||
}
|
}
|
||||||
|
Err(_) => {
|
||||||
|
render_message("This paste requires a password.".into());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -147,7 +152,22 @@ async fn fetch_resources(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let decrypted = decrypt(data, key, password)?;
|
let decrypted = match decrypt(data, &key, password) {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(e) => {
|
||||||
|
let msg = match e {
|
||||||
|
CryptoError::Password => "The provided password was incorrect.",
|
||||||
|
CryptoError::SecretKey => "The secret key in the URL was incorrect.",
|
||||||
|
ref e => {
|
||||||
|
log!(format!("Bad kdf or corrupted blob: {}", e));
|
||||||
|
"An internal error occurred."
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render_message(JsString::from(msg));
|
||||||
|
bail!(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
let db_open_req = open_idb()?;
|
let db_open_req = open_idb()?;
|
||||||
|
|
||||||
// On success callback
|
// On success callback
|
||||||
|
@ -192,7 +212,7 @@ async fn fetch_resources(
|
||||||
"entries",
|
"entries",
|
||||||
JsValue::from(
|
JsValue::from(
|
||||||
entries
|
entries
|
||||||
.into_iter()
|
.iter()
|
||||||
.filter_map(|x| JsValue::from_serde(x).ok())
|
.filter_map(|x| JsValue::from_serde(x).ok())
|
||||||
.collect::<Array>(),
|
.collect::<Array>(),
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in a new issue