aead
This commit is contained in:
parent
71201c96f3
commit
5fb711bd85
6 changed files with 400 additions and 51 deletions
53
Cargo.lock
generated
53
Cargo.lock
generated
|
@ -218,6 +218,29 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-extra"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "895ff42f72016617773af68fb90da2a9677d89c62338ec09162d4909d86fdd8f"
|
||||||
|
dependencies = [
|
||||||
|
"axum 0.7.4",
|
||||||
|
"axum-core 0.4.3",
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.0.0",
|
||||||
|
"http-body 1.0.0",
|
||||||
|
"http-body-util",
|
||||||
|
"mime",
|
||||||
|
"pin-project-lite",
|
||||||
|
"serde",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-macros"
|
name = "axum-macros"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
@ -294,6 +317,7 @@ name = "bin"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.7.4",
|
"axum 0.7.4",
|
||||||
|
"axum-extra",
|
||||||
"axum-oidc",
|
"axum-oidc",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chacha20",
|
"chacha20",
|
||||||
|
@ -306,6 +330,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"markdown",
|
"markdown",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"poly1305",
|
||||||
"prometheus-client",
|
"prometheus-client",
|
||||||
"rand",
|
"rand",
|
||||||
"render",
|
"render",
|
||||||
|
@ -320,6 +345,7 @@ dependencies = [
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tower-sessions",
|
"tower-sessions",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1652,6 +1678,12 @@ version = "1.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "open"
|
name = "open"
|
||||||
version = "5.0.1"
|
version = "5.0.1"
|
||||||
|
@ -1853,6 +1885,17 @@ version = "3.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c"
|
checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c"
|
||||||
|
|
||||||
|
[[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 = "powerfmt"
|
name = "powerfmt"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -2907,6 +2950,16 @@ dependencies = [
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "universal-hash"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
|
|
@ -32,6 +32,9 @@ sha3 = "0.10"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
bytes = "1.5"
|
bytes = "1.5"
|
||||||
pin-project-lite = "0.2"
|
pin-project-lite = "0.2"
|
||||||
|
poly1305 = "0.8.0"
|
||||||
|
zeroize = "1.7.0"
|
||||||
|
axum-extra = { version="0.9.2", features=["async-read-body"]}
|
||||||
|
|
||||||
reqwest = { version="0.11", default_features=false, features=["rustls-tls", "json"] }
|
reqwest = { version="0.11", default_features=false, features=["rustls-tls", "json"] }
|
||||||
jsonwebtoken = "9.2.0"
|
jsonwebtoken = "9.2.0"
|
||||||
|
|
|
@ -21,6 +21,9 @@ pub enum Error {
|
||||||
#[error("file exists")]
|
#[error("file exists")]
|
||||||
DataFileExists,
|
DataFileExists,
|
||||||
|
|
||||||
|
#[error("bin verification failed")]
|
||||||
|
BinVerificationFailed,
|
||||||
|
|
||||||
#[error("hex error: {:?}", 0)]
|
#[error("hex error: {:?}", 0)]
|
||||||
Hex(#[from] hex::FromHexError),
|
Hex(#[from] hex::FromHexError),
|
||||||
|
|
||||||
|
@ -59,6 +62,11 @@ impl IntoResponse for Error {
|
||||||
fn into_response(self) -> axum::response::Response {
|
fn into_response(self) -> axum::response::Response {
|
||||||
debug!("{:?}", &self);
|
debug!("{:?}", &self);
|
||||||
match self {
|
match self {
|
||||||
|
Self::BinVerificationFailed => (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"bin verification failed\n",
|
||||||
|
)
|
||||||
|
.into_response(),
|
||||||
Self::PhraseInvalid => (StatusCode::BAD_REQUEST, "url is not valid\n").into_response(),
|
Self::PhraseInvalid => (StatusCode::BAD_REQUEST, "url is not valid\n").into_response(),
|
||||||
Self::BinNotFound => (StatusCode::NOT_FOUND, "bin does not exist\n").into_response(),
|
Self::BinNotFound => (StatusCode::NOT_FOUND, "bin does not exist\n").into_response(),
|
||||||
Self::DataFileExists => {
|
Self::DataFileExists => {
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
#![deny(clippy::unwrap_used)]
|
#![deny(clippy::unwrap_used)]
|
||||||
|
use axum_extra::body::AsyncReadBody;
|
||||||
use axum_oidc::{
|
use axum_oidc::{
|
||||||
error::{ExtractorError, MiddlewareError},
|
error::{ExtractorError, MiddlewareError},
|
||||||
EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcLoginLayer,
|
EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcLoginLayer,
|
||||||
};
|
};
|
||||||
use duration_str::{deserialize_option_duration, parse_std};
|
use duration_str::parse_std;
|
||||||
use jwt::JwtApplication;
|
use jwt::JwtApplication;
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
use poly1305::{universal_hash::UniversalHash, Poly1305, Tag};
|
||||||
use prometheus_client::{
|
use prometheus_client::{
|
||||||
encoding::EncodeLabelSet,
|
encoding::EncodeLabelSet,
|
||||||
metrics::{counter::Counter, family::Family, gauge::Gauge},
|
metrics::{counter::Counter, family::Family, gauge::Gauge},
|
||||||
|
@ -14,8 +17,11 @@ use sailfish::TemplateOnce;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Borrow,
|
borrow::Borrow,
|
||||||
env,
|
env,
|
||||||
|
io::{ErrorKind, SeekFrom},
|
||||||
|
pin::Pin,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
task::{ready, Context, Poll},
|
||||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
|
@ -33,39 +39,40 @@ use axum::{
|
||||||
HeaderMap, HeaderValue, Request, StatusCode, Uri,
|
HeaderMap, HeaderValue, Request, StatusCode, Uri,
|
||||||
},
|
},
|
||||||
response::{Html, IntoResponse, Redirect, Response},
|
response::{Html, IntoResponse, Redirect, Response},
|
||||||
routing::{delete, get, post},
|
routing::{delete, get},
|
||||||
BoxError, Router,
|
Router,
|
||||||
};
|
};
|
||||||
|
|
||||||
use chacha20::{
|
use chacha20::{
|
||||||
cipher::{KeyIvInit, StreamCipher},
|
cipher::{generic_array::GenericArray, KeyInit, KeyIvInit, StreamCipher, StreamCipherSeek},
|
||||||
ChaCha20,
|
ChaCha20,
|
||||||
};
|
};
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use garbage_collector::GarbageCollector;
|
use garbage_collector::GarbageCollector;
|
||||||
use log::{debug, warn};
|
use log::{debug, info, warn};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sha3::{Digest, Sha3_256};
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
io::{AsyncWriteExt, BufReader, BufWriter},
|
io::{
|
||||||
|
AsyncRead, AsyncReadExt, AsyncSeekExt, AsyncWrite, AsyncWriteExt, BufReader, BufWriter,
|
||||||
|
ReadBuf,
|
||||||
|
},
|
||||||
net::TcpListener,
|
net::TcpListener,
|
||||||
};
|
};
|
||||||
use util::{IdSalt, KeySalt};
|
use util::{IdSalt, KeySalt};
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::Error,
|
error::Error,
|
||||||
jwt::Claims,
|
jwt::Claims,
|
||||||
metadata::Metadata,
|
metadata::Metadata,
|
||||||
util::{Id, Key, Nonce, Phrase},
|
util::{Id, Key, Nonce, Phrase},
|
||||||
web_util::DecryptingStream,
|
|
||||||
};
|
};
|
||||||
mod error;
|
mod error;
|
||||||
mod garbage_collector;
|
mod garbage_collector;
|
||||||
mod jwt;
|
mod jwt;
|
||||||
mod metadata;
|
mod metadata;
|
||||||
mod util;
|
mod util;
|
||||||
mod web_util;
|
|
||||||
|
|
||||||
/// length of the "phrase" that is used to access the bin (https://example.com/<phrase>)
|
/// length of the "phrase" that is used to access the bin (https://example.com/<phrase>)
|
||||||
const PHRASE_LENGTH: usize = 16;
|
const PHRASE_LENGTH: usize = 16;
|
||||||
|
@ -242,7 +249,7 @@ async fn get_index(
|
||||||
let metadata = Metadata {
|
let metadata = Metadata {
|
||||||
subject,
|
subject,
|
||||||
nonce: nonce.to_hex(),
|
nonce: nonce.to_hex(),
|
||||||
etag: None,
|
tag: None,
|
||||||
size: None,
|
size: None,
|
||||||
content_type: None,
|
content_type: None,
|
||||||
expires_at,
|
expires_at,
|
||||||
|
@ -329,30 +336,25 @@ async fn upload_bin(
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
let key = Key::from_phrase(&phrase, &app_state.key_salt);
|
let key = Key::from_phrase(&phrase, &app_state.key_salt);
|
||||||
let nonce = Nonce::from_hex(&metadata.nonce)?;
|
let nonce = Nonce::from_hex(&metadata.nonce)?;
|
||||||
let mut cipher = ChaCha20::new(key.borrow(), nonce.borrow());
|
|
||||||
|
|
||||||
let file = File::create(&path).await?;
|
let file = File::create(&path).await?;
|
||||||
let mut writer = BufWriter::new(file);
|
let writer = BufWriter::new(file);
|
||||||
|
|
||||||
let mut etag_hasher = Sha3_256::new();
|
|
||||||
let mut size: u64 = 0;
|
|
||||||
|
|
||||||
let mut ttl = params
|
let mut ttl = params
|
||||||
.ttl
|
.ttl
|
||||||
.map(|x| duration_str::parse(&x).map_err(|_| Error::InvalidTtl))
|
.map(|x| duration_str::parse(x).map_err(|_| Error::InvalidTtl))
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or(Duration::from_secs(24 * 3600));
|
.unwrap_or(Duration::from_secs(24 * 3600));
|
||||||
|
|
||||||
|
let mut writer = EncWriter::new(writer, key, nonce, &[]);
|
||||||
|
|
||||||
match data {
|
match data {
|
||||||
MultipartOrStream::Stream(stream) => {
|
MultipartOrStream::Stream(stream) => {
|
||||||
let mut stream = stream.into_data_stream();
|
let mut stream = stream.into_data_stream();
|
||||||
while let Some(chunk) = stream.next().await {
|
while let Some(chunk) = stream.next().await {
|
||||||
let mut buf = chunk.unwrap_or_default().to_vec();
|
writer.write_all(chunk.unwrap_or_default().as_ref()).await?;
|
||||||
etag_hasher.update(&buf);
|
|
||||||
size += buf.len() as u64;
|
|
||||||
cipher.apply_keystream(&mut buf);
|
|
||||||
writer.write_all(&buf).await?;
|
|
||||||
}
|
}
|
||||||
|
writer.shutdown().await?;
|
||||||
metadata.content_type = match headers.get(CONTENT_TYPE) {
|
metadata.content_type = match headers.get(CONTENT_TYPE) {
|
||||||
Some(content_type) => Some(
|
Some(content_type) => Some(
|
||||||
content_type
|
content_type
|
||||||
|
@ -373,11 +375,7 @@ async fn upload_bin(
|
||||||
{
|
{
|
||||||
if field.name().unwrap_or_default() == "file" && !file_read {
|
if field.name().unwrap_or_default() == "file" && !file_read {
|
||||||
while let Some(chunk) = field.chunk().await.unwrap_or_default() {
|
while let Some(chunk) = field.chunk().await.unwrap_or_default() {
|
||||||
let mut buf = chunk.to_vec();
|
writer.write_all(chunk.as_ref()).await?;
|
||||||
etag_hasher.update(&buf);
|
|
||||||
size += buf.len() as u64;
|
|
||||||
cipher.apply_keystream(&mut buf);
|
|
||||||
writer.write_all(&buf).await?;
|
|
||||||
}
|
}
|
||||||
metadata.content_type = Some(
|
metadata.content_type = Some(
|
||||||
field
|
field
|
||||||
|
@ -398,6 +396,9 @@ async fn upload_bin(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writer.flush().await?;
|
writer.flush().await?;
|
||||||
|
writer.shutdown().await?;
|
||||||
|
|
||||||
|
let tag = writer.tag().expect("valid tag");
|
||||||
|
|
||||||
if let Some(expires_at) = SystemTime::now()
|
if let Some(expires_at) = SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)?
|
.duration_since(UNIX_EPOCH)?
|
||||||
|
@ -407,8 +408,8 @@ async fn upload_bin(
|
||||||
metadata.expires_at = expires_at;
|
metadata.expires_at = expires_at;
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata.etag = Some(hex::encode(etag_hasher.finalize()));
|
metadata.tag = Some(hex::encode(tag));
|
||||||
metadata.size = Some(size);
|
metadata.size = Some(writer.size() as u64);
|
||||||
|
|
||||||
metadata.to_file(&metadata_path).await?;
|
metadata.to_file(&metadata_path).await?;
|
||||||
|
|
||||||
|
@ -459,15 +460,41 @@ async fn get_item(
|
||||||
} else {
|
} else {
|
||||||
//TODO(pfz4): Maybe add link handling
|
//TODO(pfz4): Maybe add link handling
|
||||||
let file = File::open(&path).await?;
|
let file = File::open(&path).await?;
|
||||||
let reader = BufReader::new(file);
|
let mut reader = BufReader::new(file);
|
||||||
|
|
||||||
let body = Body::from_stream(DecryptingStream::new(
|
let tag = *Tag::from_slice(
|
||||||
reader,
|
&hex::decode(metadata.tag.as_deref().unwrap_or("")).unwrap_or_default(),
|
||||||
id.clone(),
|
);
|
||||||
&metadata,
|
|
||||||
&key,
|
{
|
||||||
&nonce,
|
let mut cipher = ChaCha20::new(key.borrow(), nonce.borrow());
|
||||||
));
|
let mut mac_key = poly1305::Key::default();
|
||||||
|
cipher.apply_keystream(&mut mac_key);
|
||||||
|
|
||||||
|
let mut mac = Poly1305::new(GenericArray::from_slice(&mac_key));
|
||||||
|
mac_key.zeroize();
|
||||||
|
|
||||||
|
mac.update_padded(&[]);
|
||||||
|
|
||||||
|
let mut buf = [0_u8; 512];
|
||||||
|
let mut read_length = 0;
|
||||||
|
while let Ok(read) = reader.read(&mut buf).await {
|
||||||
|
if read == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mac.update_padded(&buf[..read]);
|
||||||
|
read_length += read;
|
||||||
|
}
|
||||||
|
debug!("{}", read_length);
|
||||||
|
|
||||||
|
authenticate_lengths(&mut mac, 0, read_length);
|
||||||
|
|
||||||
|
mac.verify(&tag).map_err(|_| Error::BinVerificationFailed)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.seek(SeekFrom::Start(0)).await?;
|
||||||
|
|
||||||
|
let body = AsyncReadBody::new(DecReader::new(reader, key, nonce, &[], tag));
|
||||||
|
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
headers.insert(
|
headers.insert(
|
||||||
|
@ -481,15 +508,6 @@ async fn get_item(
|
||||||
if let Some(content_type) = metadata.content_type.and_then(|x| x.parse().ok()) {
|
if let Some(content_type) = metadata.content_type.and_then(|x| x.parse().ok()) {
|
||||||
headers.insert(header::CONTENT_TYPE, content_type);
|
headers.insert(header::CONTENT_TYPE, content_type);
|
||||||
}
|
}
|
||||||
if let Some(etag) = metadata.etag.clone().and_then(|x| x.parse().ok()) {
|
|
||||||
headers.insert(header::ETAG, etag);
|
|
||||||
}
|
|
||||||
if let Some(digest) = metadata
|
|
||||||
.etag
|
|
||||||
.and_then(|x| format!("sha3-256={x}").parse().ok())
|
|
||||||
{
|
|
||||||
headers.insert("Digest", digest);
|
|
||||||
}
|
|
||||||
|
|
||||||
app_state
|
app_state
|
||||||
.metrics
|
.metrics
|
||||||
|
@ -559,3 +577,274 @@ pub struct IndexTemplate<'a> {
|
||||||
#[derive(TemplateOnce)]
|
#[derive(TemplateOnce)]
|
||||||
#[template(path = "delete.stpl")]
|
#[template(path = "delete.stpl")]
|
||||||
pub struct DeleteTemplate;
|
pub struct DeleteTemplate;
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
struct EncWriter<T: AsyncWrite> {
|
||||||
|
#[pin]
|
||||||
|
inner: T,
|
||||||
|
buf: Vec<u8>,
|
||||||
|
to_mac: Vec<u8>,
|
||||||
|
cipher: ChaCha20,
|
||||||
|
mac: Poly1305,
|
||||||
|
tag: Option<Tag>,
|
||||||
|
encrypted: usize,
|
||||||
|
authenticated_data_len: usize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsyncWrite> EncWriter<T> {
|
||||||
|
pub fn new(inner: T, key: Key, nonce: Nonce, authenticated_data: &[u8]) -> Self {
|
||||||
|
let mut cipher = ChaCha20::new(key.borrow(), nonce.borrow());
|
||||||
|
let mut mac_key = poly1305::Key::default();
|
||||||
|
cipher.apply_keystream(&mut mac_key);
|
||||||
|
|
||||||
|
let mut mac = Poly1305::new(GenericArray::from_slice(&mac_key));
|
||||||
|
mac_key.zeroize();
|
||||||
|
|
||||||
|
mac.update_padded(authenticated_data);
|
||||||
|
|
||||||
|
// Set ChaCha20 counter to 1
|
||||||
|
cipher.seek(64);
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
buf: vec![],
|
||||||
|
to_mac: vec![],
|
||||||
|
authenticated_data_len: authenticated_data.len(),
|
||||||
|
encrypted: 0,
|
||||||
|
cipher,
|
||||||
|
mac,
|
||||||
|
tag: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn tag(&self) -> Option<&Tag> {
|
||||||
|
self.tag.as_ref()
|
||||||
|
}
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
self.encrypted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsyncWrite> AsyncWrite for EncWriter<T> {
|
||||||
|
fn poll_write(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Poll<Result<usize, std::io::Error>> {
|
||||||
|
let me = self.project();
|
||||||
|
|
||||||
|
if me.buf.len() > 16 {
|
||||||
|
return Poll::Pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buf = Vec::from(buf);
|
||||||
|
|
||||||
|
me.cipher.apply_keystream(&mut buf);
|
||||||
|
|
||||||
|
me.to_mac.extend_from_slice(&buf);
|
||||||
|
let macable = me.to_mac.drain(..align(buf.len(), 16)).collect::<Vec<_>>();
|
||||||
|
debug!("645: {}", macable.len() % 16);
|
||||||
|
me.mac.update_padded(&macable);
|
||||||
|
|
||||||
|
*me.encrypted += buf.len();
|
||||||
|
let buf_len = buf.len();
|
||||||
|
|
||||||
|
me.buf.append(&mut buf);
|
||||||
|
|
||||||
|
if let Poll::Ready(Ok(written)) = me.inner.poll_write(cx, me.buf) {
|
||||||
|
me.buf.drain(..written);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("after write: {}", buf_len);
|
||||||
|
|
||||||
|
Poll::Ready(Ok(buf_len))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), std::io::Error>> {
|
||||||
|
let me = self.project();
|
||||||
|
|
||||||
|
debug!("flush");
|
||||||
|
|
||||||
|
if !me.buf.is_empty() {
|
||||||
|
let written = ready!(me.inner.poll_write(cx, me.buf))?;
|
||||||
|
me.buf.drain(..written);
|
||||||
|
Poll::Pending
|
||||||
|
} else {
|
||||||
|
me.inner.poll_flush(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_shutdown(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Result<(), std::io::Error>> {
|
||||||
|
let me = self.project();
|
||||||
|
|
||||||
|
debug!("shutdown");
|
||||||
|
|
||||||
|
if !me.buf.is_empty() {
|
||||||
|
let written = ready!(me.inner.poll_write(cx, me.buf))?;
|
||||||
|
me.buf.drain(..written);
|
||||||
|
Poll::Pending
|
||||||
|
} else if me.tag.is_none() {
|
||||||
|
me.mac.update_padded(me.to_mac);
|
||||||
|
me.to_mac.clear();
|
||||||
|
|
||||||
|
authenticate_lengths(me.mac, *me.authenticated_data_len, *me.encrypted);
|
||||||
|
|
||||||
|
*me.tag = Some(me.mac.clone().finalize());
|
||||||
|
Poll::Pending
|
||||||
|
} else {
|
||||||
|
me.inner.poll_shutdown(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
struct DecReader<T: AsyncRead> {
|
||||||
|
#[pin]
|
||||||
|
inner: T,
|
||||||
|
buf: Vec<u8>,
|
||||||
|
cipher: ChaCha20,
|
||||||
|
mac: Poly1305,
|
||||||
|
decrypted: usize,
|
||||||
|
authenticated_data_len: usize,
|
||||||
|
tag: Tag,
|
||||||
|
state: DecReaderState,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsyncRead> DecReader<T> {
|
||||||
|
pub fn new(inner: T, key: Key, nonce: Nonce, authenticated_data: &[u8], tag: Tag) -> Self {
|
||||||
|
let mut cipher = ChaCha20::new(key.borrow(), nonce.borrow());
|
||||||
|
let mut mac_key = poly1305::Key::default();
|
||||||
|
cipher.apply_keystream(&mut mac_key);
|
||||||
|
|
||||||
|
let mut mac = Poly1305::new(GenericArray::from_slice(&mac_key));
|
||||||
|
mac_key.zeroize();
|
||||||
|
|
||||||
|
mac.update_padded(authenticated_data);
|
||||||
|
|
||||||
|
// Set ChaCha20 counter to 1
|
||||||
|
cipher.seek(64);
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
buf: vec![0_u8; 512],
|
||||||
|
cipher,
|
||||||
|
mac,
|
||||||
|
authenticated_data_len: authenticated_data.len(),
|
||||||
|
decrypted: 0,
|
||||||
|
state: DecReaderState::Running,
|
||||||
|
tag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum DecReaderState {
|
||||||
|
Running,
|
||||||
|
Finished,
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsyncRead> AsyncRead for DecReader<T> {
|
||||||
|
fn poll_read(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &mut ReadBuf<'_>,
|
||||||
|
) -> Poll<std::io::Result<()>> {
|
||||||
|
let me = self.project();
|
||||||
|
|
||||||
|
//TODO move buf.filled to struct
|
||||||
|
|
||||||
|
match me.state {
|
||||||
|
DecReaderState::Finished => Poll::Ready(Ok(())),
|
||||||
|
DecReaderState::Failed => Poll::Ready(Err(std::io::Error::new(
|
||||||
|
ErrorKind::InvalidData,
|
||||||
|
"decryption failed",
|
||||||
|
))),
|
||||||
|
DecReaderState::Running => {
|
||||||
|
let changed = {
|
||||||
|
me.buf.extend_from_slice(&[0_u8; 512]);
|
||||||
|
let mut read_buf = ReadBuf::new(me.buf);
|
||||||
|
|
||||||
|
let prev_buf_len = read_buf.filled().len();
|
||||||
|
ready!(me.inner.poll_read(cx, &mut read_buf))?;
|
||||||
|
let buf_len = read_buf.filled().len();
|
||||||
|
drop(read_buf);
|
||||||
|
me.buf.drain(buf_len..);
|
||||||
|
prev_buf_len == buf_len
|
||||||
|
};
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
let decryptable = me.buf;
|
||||||
|
me.mac.update_padded(decryptable);
|
||||||
|
me.cipher.apply_keystream(decryptable);
|
||||||
|
|
||||||
|
*me.decrypted += decryptable.len();
|
||||||
|
|
||||||
|
buf.put_slice(decryptable);
|
||||||
|
|
||||||
|
authenticate_lengths(me.mac, *me.authenticated_data_len, *me.decrypted);
|
||||||
|
|
||||||
|
match me.mac.clone().verify(me.tag) {
|
||||||
|
Ok(_) => {
|
||||||
|
*me.state = DecReaderState::Finished;
|
||||||
|
|
||||||
|
warn!("finished");
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
*me.state = DecReaderState::Failed;
|
||||||
|
warn!("decryption failed");
|
||||||
|
|
||||||
|
Poll::Ready(Err(std::io::Error::new(
|
||||||
|
ErrorKind::InvalidData,
|
||||||
|
"decryption failed",
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let (decryptable, residual) = {
|
||||||
|
let decryptable_buffer_len = align(min(buf.remaining(), me.buf.len()), 16);
|
||||||
|
me.buf.split_at_mut(decryptable_buffer_len)
|
||||||
|
};
|
||||||
|
if !decryptable.is_empty() {
|
||||||
|
me.mac.update_padded(decryptable);
|
||||||
|
me.cipher.apply_keystream(decryptable);
|
||||||
|
|
||||||
|
*me.decrypted += decryptable.len();
|
||||||
|
|
||||||
|
buf.put_slice(decryptable);
|
||||||
|
|
||||||
|
{
|
||||||
|
let decryptable_len = decryptable.len();
|
||||||
|
let residual_len = residual.len();
|
||||||
|
me.buf.copy_within(decryptable_len.., 0);
|
||||||
|
me.buf.drain(residual_len..);
|
||||||
|
}
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
} else {
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn min(a: usize, b: usize) -> usize {
|
||||||
|
match a < b {
|
||||||
|
true => a,
|
||||||
|
false => b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn align(val: usize, m: usize) -> usize {
|
||||||
|
val - (val % m)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn authenticate_lengths(mac: &mut Poly1305, associated_data_len: usize, buffer_len: usize) {
|
||||||
|
let mut block = GenericArray::default();
|
||||||
|
block[..8].copy_from_slice(&associated_data_len.to_le_bytes());
|
||||||
|
block[8..].copy_from_slice(&buffer_len.to_le_bytes());
|
||||||
|
mac.update(&[block]);
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::Error;
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
pub subject: String,
|
pub subject: String,
|
||||||
pub nonce: String,
|
pub nonce: String,
|
||||||
pub etag: Option<String>,
|
pub tag: Option<String>,
|
||||||
pub size: Option<u64>,
|
pub size: Option<u64>,
|
||||||
pub content_type: Option<String>,
|
pub content_type: Option<String>,
|
||||||
pub expires_at: u64, // seconds since UNIX_EPOCH
|
pub expires_at: u64, // seconds since UNIX_EPOCH
|
||||||
|
|
|
@ -29,8 +29,6 @@ pin_project! {
|
||||||
capacity: usize,
|
capacity: usize,
|
||||||
// chacha20 cipher
|
// chacha20 cipher
|
||||||
cipher: ChaCha20,
|
cipher: ChaCha20,
|
||||||
// hasher to verify file integrity
|
|
||||||
hasher: Sha3_256,
|
|
||||||
// hash to verify against
|
// hash to verify against
|
||||||
target_hash: String,
|
target_hash: String,
|
||||||
// id of the file for logging purposes
|
// id of the file for logging purposes
|
||||||
|
@ -49,8 +47,7 @@ impl<R: AsyncRead> DecryptingStream<R> {
|
||||||
buf: BytesMut::new(),
|
buf: BytesMut::new(),
|
||||||
capacity: 1 << 22, // 4 MiB
|
capacity: 1 << 22, // 4 MiB
|
||||||
cipher,
|
cipher,
|
||||||
hasher: Sha3_256::new(),
|
target_hash: metadata.tag.clone().unwrap_or_default(),
|
||||||
target_hash: metadata.etag.clone().unwrap_or_default(),
|
|
||||||
id,
|
id,
|
||||||
size: metadata.size.unwrap_or_default(),
|
size: metadata.size.unwrap_or_default(),
|
||||||
progress: 0,
|
progress: 0,
|
||||||
|
@ -94,7 +91,6 @@ impl<R: AsyncRead> Stream for DecryptingStream<R> {
|
||||||
// decrypt the chunk using chacha
|
// decrypt the chunk using chacha
|
||||||
this.cipher.apply_keystream(&mut chunk);
|
this.cipher.apply_keystream(&mut chunk);
|
||||||
// update the sha3 hasher
|
// update the sha3 hasher
|
||||||
this.hasher.update(&chunk);
|
|
||||||
// track progress
|
// track progress
|
||||||
*this.progress += n as u64;
|
*this.progress += n as u64;
|
||||||
if self.progress_check() == DecryptingStreamProgress::Failed {
|
if self.progress_check() == DecryptingStreamProgress::Failed {
|
||||||
|
|
Loading…
Reference in a new issue