Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CI linting with clippy and rustfmt #5

Merged
merged 9 commits into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/lints.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: CI:clippy+rustfmt

on:
pull_request:
push:
branches:
- main

# Make sure CI fails on all warnings, including Clippy lints
env:
RUSTFLAGS: "-Dwarnings"

jobs:
linting:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
components: rustfmt, clippy

- name: Run rust clippy
run: |
cargo +nightly clippy --all-targets --all-features

- name: Run rust fmt
run: |
cargo +nightly fmt --check
71 changes: 51 additions & 20 deletions src/api.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use std::str::FromStr;
use rand_chacha::rand_core::{RngCore, SeedableRng};
use reqwest::{Method, Url};
use crate::crypto::encrypt;
use crate::{DecryptedPaste};
use crate::privatebin::{Paste, PostPasteResponse};
use crate::error::{PasteError, PbError, PbResult};
use crate::opts::Opts;
use crate::privatebin::{Paste, PostPasteResponse};
use crate::util::check_filesize;
use crate::DecryptedPaste;
use rand_chacha::rand_core::{RngCore, SeedableRng};
use reqwest::{Method, Url};
use std::str::FromStr;

#[derive()]
pub struct API {
Expand All @@ -18,7 +18,7 @@ impl API {
pub fn new(mut url: Url, opts: Opts) -> Self {
url.set_fragment(None);
url.set_query(None);
if !url.path().ends_with("/") {
if !url.path().ends_with('/') {
url.set_path(&format!("{}{}", url.path(), "/"))
}
Self { base: url, opts }
Expand Down Expand Up @@ -48,23 +48,36 @@ impl API {

let access_token_response: serde_json::Value = response.json()?;

let token_type = access_token_response.get("token_type").unwrap().as_str().unwrap();
let token_type = access_token_response
.get("token_type")
.unwrap()
.as_str()
.unwrap();
if !token_type.eq_ignore_ascii_case("bearer") {
return Err(PbError::InvalidTokenType(token_type.to_string()));
}

let token: String = access_token_response.get("access_token").unwrap().as_str().unwrap().to_string();
let token: String = access_token_response
.get("access_token")
.unwrap()
.as_str()
.unwrap()
.to_string();

Ok(token)
}

fn preconfigured_privatebin_request_builder(&self, method: &str, url: Url) -> PbResult<reqwest::blocking::RequestBuilder> {
fn preconfigured_privatebin_request_builder(
&self,
method: &str,
url: Url,
) -> PbResult<reqwest::blocking::RequestBuilder> {
let client = reqwest::blocking::Client::builder().build()?;

let mut request = client.request(Method::from_str(method).unwrap(), url);
request = request.header("X-Requested-With", "JSONHttpRequest");

if let Some(_) = &self.opts.oidc_token_url {
if self.opts.oidc_token_url.is_some() {
let access_token = self.get_oidc_access_token()?;
let auth_header = ["Bearer".into(), access_token].join(" ");
request = request.header("Authorization", auth_header)
Expand All @@ -74,8 +87,11 @@ impl API {
}

pub fn get_paste(&self, paste_id: &str) -> PbResult<Paste> {
let url = reqwest::Url::parse_with_params(&self.base.as_str(), [("pasteid", paste_id)])?;
let value: serde_json::Value = self.preconfigured_privatebin_request_builder("GET", url)?.send()?.json()?;
let url = reqwest::Url::parse_with_params(self.base.as_str(), [("pasteid", paste_id)])?;
let value: serde_json::Value = self
.preconfigured_privatebin_request_builder("GET", url)?
.send()?
.json()?;
let status: u32 = value.get("status").unwrap().as_u64().unwrap() as u32;

match status {
Expand All @@ -85,7 +101,12 @@ impl API {
}
}

pub fn post_paste(&self, content: &DecryptedPaste, password: &str, opts: &Opts) -> PbResult<PostPasteResponse> {
pub fn post_paste(
&self,
content: &DecryptedPaste,
password: &str,
opts: &Opts,
) -> PbResult<PostPasteResponse> {
let mut rng = rand_chacha::ChaCha20Rng::from_entropy();
let mut paste_passphrase = [0u8; 32];
let mut kdf_salt = [0u8; 8];
Expand All @@ -98,24 +119,34 @@ impl API {

let mut post_body = serde_json::json!({
"v": 2,
"adata": [[base64::encode(&nonce),base64::encode(&kdf_salt),100000,256,128,"aes","gcm","zlib"],opts.format,opts.discussion as u8,opts.burn as u8],
"adata": [[base64::encode(nonce),base64::encode(kdf_salt),100000,256,128,"aes","gcm","zlib"],opts.format,opts.discussion as u8,opts.burn as u8],
"ct": "",
"meta": {
"expire": opts.expire
}
});
let adata = post_body.get("adata").unwrap().to_string();
let encrypted_content = encrypt(&serde_json::to_string(content)?, &paste_passphrase.into(), password, &kdf_salt.into(), &nonce.into(), iterations, &adata)?;

let b64_encrpyed_content = base64::encode(&encrypted_content);
let encrypted_content = encrypt(
&serde_json::to_string(content)?,
&paste_passphrase.into(),
password,
&kdf_salt,
&nonce,
iterations,
&adata,
)?;

let b64_encrpyed_content = base64::encode(encrypted_content);

check_filesize(b64_encrpyed_content.len() as u64, opts.size_limit);

post_body["ct"] = b64_encrpyed_content.into();


let url = self.base.clone();
let response = self.preconfigured_privatebin_request_builder("POST", url)?.body::<String>(serde_json::to_string(&post_body).unwrap()).send()?;
let response = self
.preconfigured_privatebin_request_builder("POST", url)?
.body::<String>(serde_json::to_string(&post_body).unwrap())
.send()?;
let mut rsv: serde_json::Value = response.json()?;
rsv["bs58key"] = serde_json::Value::String(bs58::encode(paste_passphrase).into_string());
let status: u32 = rsv.get("status").unwrap().as_u64().unwrap() as u32;
Expand All @@ -130,4 +161,4 @@ impl API {
pub fn base(&self) -> &Url {
&self.base
}
}
}
13 changes: 6 additions & 7 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
use std::env;
use std::ffi::{ OsString};
use std::ffi::OsString;
use std::io::BufRead;

pub fn get_args() -> Vec<OsString> {
let mut env_args = std::env::args_os();


let path = match env::var_os("PBCLI_CONFIG_PATH") {
None => return env_args.collect(),
Some(path) => path
Some(path) => path,
};

let handle = match std::fs::File::open(path) {
Ok(file) => file,
Err(_) => return env_args.collect() // TODO: Raise error
Err(_) => return env_args.collect(), // TODO: Raise error
};

let reader = std::io::BufReader::new(handle);
let mut config_args: Vec<OsString> = vec![];
reader.lines().for_each(|line| {
let line = match line {
Ok(line) => line.trim().to_owned(),
Err(_) => return
Err(_) => return,
};

if line.starts_with("#") {
if line.starts_with('#') {
return;
}

Expand All @@ -38,4 +37,4 @@ pub fn get_args() -> Vec<OsString> {
config_args.extend(env_args);

config_args
}
}
46 changes: 30 additions & 16 deletions src/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use aes_gcm::{Key, Nonce};
use crate::error::{PasteError, PbResult};
use crate::privatebin::{DecryptedPaste, Paste};
use aes_gcm::aead::{Aead, NewAead};
use aes_gcm::{Key, Nonce};

fn derive_key(iterations: std::num::NonZeroU32, salt: &[u8], key: &[u8], out: &mut [u8]) -> () {
ring::pbkdf2::derive(ring::pbkdf2::PBKDF2_HMAC_SHA256, iterations, &salt, &key, out);
fn derive_key(iterations: std::num::NonZeroU32, salt: &[u8], key: &[u8], out: &mut [u8]) {
ring::pbkdf2::derive(ring::pbkdf2::PBKDF2_HMAC_SHA256, iterations, salt, key, out);
}

pub fn decrypt_with_password(paste: &Paste, key: &[u8], password: &str) -> PbResult<DecryptedPaste> {
pub fn decrypt_with_password(
paste: &Paste,
key: &[u8],
password: &str,
) -> PbResult<DecryptedPaste> {
let cipher_algo = &paste.adata.cipher.cipher_algo;
let cipher_mode = &paste.adata.cipher.cipher_mode;
let kdf_keysize = paste.adata.cipher.kdf_keysize;
Expand All @@ -22,32 +26,42 @@ pub fn decrypt_with_password(paste: &Paste, key: &[u8], password: &str) -> PbRes

match (&cipher_algo[..], &cipher_mode[..], kdf_keysize) {
("aes", "gcm", 256) => decrypt_aes_256_gcm(paste, &derived_key),
_ => Err(
PasteError::CipherNotImplemented {
cipher_mode: paste.adata.cipher.cipher_mode.clone(),
cipher_algo: paste.adata.cipher.cipher_algo.clone(),
keysize: paste.adata.cipher.kdf_keysize,
}
),
_ => Err(PasteError::CipherNotImplemented {
cipher_mode: paste.adata.cipher.cipher_mode.clone(),
cipher_algo: paste.adata.cipher.cipher_algo.clone(),
keysize: paste.adata.cipher.kdf_keysize,
}),
}
}

pub fn encrypt(content: &str, key: &Vec<u8>, password: &str, salt: &Vec<u8>, nonce: &Vec<u8>, iterations: u32, aad: &str) -> PbResult<Vec<u8>> {
pub fn encrypt(
content: &str,
key: &Vec<u8>,
password: &str,
salt: &[u8],
nonce: &[u8],
iterations: u32,
aad: &str,
) -> PbResult<Vec<u8>> {
let paste_blob = miniz_oxide::deflate::compress_to_vec(content.as_bytes(), 10);

let key = [key, password.as_bytes()].concat();


let mut derived_key = [0u8; 32];
derive_key(std::num::NonZeroU32::new(iterations).unwrap(), &salt, &key, &mut derived_key);
derive_key(
std::num::NonZeroU32::new(iterations).unwrap(),
salt,
&key,
&mut derived_key,
);

type Cipher = aes_gcm::AesGcm<aes_gcm::aes::Aes256, typenum::U16>;
let cipher = Cipher::new(Key::from_slice(&derived_key));
let payload = aes_gcm::aead::Payload {
msg: &paste_blob,
aad: aad.as_bytes(),
};
let encrypted_data = cipher.encrypt(Nonce::from_slice(&nonce), payload)?;
let encrypted_data = cipher.encrypt(Nonce::from_slice(nonce), payload)?;

Ok(encrypted_data)
}
Expand All @@ -62,7 +76,7 @@ fn decrypt_aes_256_gcm(paste: &Paste, derived_key: &[u8]) -> PbResult<DecryptedP
let ciphertext = base64::decode(&paste.ct)?;
let nonce = base64::decode(&paste.adata.cipher.cipher_iv)?;

let cipher = Cipher::new(Key::from_slice(&derived_key));
let cipher = Cipher::new(Key::from_slice(derived_key));
let payload = aes_gcm::aead::Payload {
msg: &ciphertext,
aad: paste.adata_str.as_bytes(),
Expand Down
19 changes: 12 additions & 7 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::fmt::{Formatter};
use std::fmt;
use base64::DecodeError;
use data_url::DataUrlError;
use miniz_oxide::inflate::TINFLStatus;
use serde_json::Error;
use std::fmt;
use std::fmt::Formatter;

pub type PbError = PasteError;
pub type PbResult<T> = std::result::Result<T, PbError>;
Expand Down Expand Up @@ -35,7 +35,6 @@ pub enum PasteError {
OidcBadRequest(serde_json::Value),
}


impl std::error::Error for PasteError {}

impl fmt::Display for PasteError {
Expand All @@ -44,8 +43,12 @@ impl fmt::Display for PasteError {
PasteError::CipherNotImplemented {
cipher_algo,
cipher_mode,
keysize
} => write!(f, "Cipher not implemented algo: {} mode: {} keysize: {}", cipher_algo, cipher_mode, keysize),
keysize,
} => write!(
f,
"Cipher not implemented algo: {} mode: {} keysize: {}",
cipher_algo, cipher_mode, keysize
),
PasteError::Json(r) => r.fmt(f),
PasteError::Request(r) => r.fmt(f),
PasteError::Io(r) => r.fmt(f),
Expand All @@ -62,7 +65,9 @@ impl fmt::Display for PasteError {
PasteError::InvalidAttachment(err) => write!(f, "Invalid attachment: {:?}", err),
PasteError::FileExists => write!(f, "File already exists. Use --overwrite to force"),
PasteError::NotAFile => write!(f, "Given path is not a file"),
PasteError::InvalidTokenType(token_type) => write!(f, "Invalid token type: {}", token_type),
PasteError::InvalidTokenType(token_type) => {
write!(f, "Invalid token type: {}", token_type)
}
PasteError::OidcBadRequest(json) => write!(f, "{}", json),
}
}
Expand Down Expand Up @@ -120,4 +125,4 @@ impl From<DataUrlError> for PasteError {
fn from(err: DataUrlError) -> Self {
PasteError::InvalidAttachment(err)
}
}
}
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ pub mod opts;
pub mod privatebin;
pub mod util;

pub use api::API;
pub use error::{PasteError, PbResult};
pub use opts::Opts;
pub use privatebin::{DecryptedPaste, PasteFormat};
pub use error::{PasteError, PbResult};
pub use util::check_filesize;
pub use api::API;
Loading
Loading