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

Replace sodumoxide with rust-crypto alternatives #72

Merged
merged 5 commits into from
Feb 28, 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
1 change: 1 addition & 0 deletions .clippy.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
msrv = "1.70.0"
61 changes: 24 additions & 37 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,74 +4,61 @@ on:
- master
pull_request:
schedule:
- cron: '30 3 * * 2'
- cron: "30 3 * * 2"

name: CI

jobs:
env:
RUST_VERSION: "1.70.0"

jobs:
test:
name: run tests
strategy:
matrix:
rust: [1.72, stable]
rust: [$RUST_VERSION, stable]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- name: Build with default features
uses: actions-rs/cargo@v1
with:
command: build
args: --all-targets
run: cargo build --all-targets
- name: Build with all features
uses: actions-rs/cargo@v1
with:
command: build
args: --all-targets --all-features
run: cargo build --all-targets --all-features
- name: Run tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all-features
run: cargo test --all-features

fmt:
name: run rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.57
override: true
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@v1
with:
toolchain: $RUST_VERSION
components: rustfmt
- name: Check formatting in all files
run: cargo fmt --all -- --check

clippy:
name: run clippy lints
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@v1
with:
toolchain: stable
components: clippy
override: true
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
- name: Check if there are clippy warnings
run: cargo clippy --all-features -- -D warnings

audit:
name: run cargo audit
runs-on: ubuntu-latest
container: dbrgn/cargo-audit:latest
steps:
- uses: actions/checkout@v2
- run: cargo audit
- uses: actions/checkout@v4
- name: Audit all used dependencies
run: cargo audit
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ Possible log types:

### Unreleased

- [added] Re-export `crypto_secretbox::Nonce`
- [changed] Replace `sodiumoxide` with `crypto_box` and `crypto_secretbox`
- [changed] Replace re-exports of `PublicKey`, `SecretKey` and `Key`
- [changed] Use dedicated `Nonce` type instead of `&[u8; 24]`
- [changed] Return result in `encrypt_*` functions
- [changed] Use `hmac` and `sha2` crates for calculating MAC

### v0.16.0 (2023-09-04)

- [added] Expose encryption functions: `encrypt` and `encrypt_raw` (#59)
Expand Down
9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,20 @@ receive = ["form_urlencoded", "serde_urlencoded"] # Support for receiving and de

[dependencies]
byteorder = "1.0"
crypto_box = "0.9.1"
crypto_secretbox = "0.1.1"
data-encoding = "2.1"
form_urlencoded = { version = "1", optional = true }
hmac = "0.12.1"
log = "0.4"
thiserror = "1"
reqwest = { version = "0.11", features = ["rustls-tls-native-roots", "multipart"], default-features = false }
rand = "0.8.5"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_urlencoded = { version = "0.7", optional = true }
sodiumoxide = "0.2.0"
sha2 = "0.10.8"
thiserror = "1"
zeroize = { version = "1", features = ["zeroize_derive"], default-features = false }

[dev-dependencies]
docopt = "1.1.0"
Expand Down
3 changes: 2 additions & 1 deletion examples/download_blob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ async fn main() {
let bytes = HEXLOWER_PERMISSIVE
.decode(blob_key_raw.as_bytes())
.expect("Invalid blob key");
Some(Key::from_slice(&bytes).expect("Invalid blob key bytes"))
let key = Key::try_from(bytes).expect("Invalid size of blob key");
Some(key)
} else {
None
};
Expand Down
9 changes: 5 additions & 4 deletions examples/generate_keypair.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crypto_secretbox::aead::OsRng;
use data_encoding::HEXLOWER;
use sodiumoxide::crypto::box_;

fn main() {
println!("Generating new random nacl/libsodium crypto box keypair:\n");
let (pk, sk) = box_::gen_keypair();
println!(" Public: {}", HEXLOWER.encode(&pk.0));
println!(" Private: {}", HEXLOWER.encode(&sk.0));
let sk = crypto_box::SecretKey::generate(&mut OsRng);
let pk = sk.public_key();
println!(" Public: {}", HEXLOWER.encode(pk.as_bytes()));
println!(" Private: {}", HEXLOWER.encode(&sk.to_bytes()));
println!("\nKeep the private key safe, and don't share it with anybody!");
}
16 changes: 9 additions & 7 deletions examples/receive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,22 @@ async fn main() {
// Command line arguments
let our_id = args.get_str("<our-id>");
let secret = args.get_str("<secret>");
let private_key = HEXLOWER_PERMISSIVE
let key_bytes = HEXLOWER_PERMISSIVE
.decode(args.get_str("<private-key>").as_bytes())
.ok()
.and_then(|bytes| SecretKey::from_slice(&bytes))
.unwrap_or_else(|| {
eprintln!("Invalid private key");
.unwrap_or_else(|_| {
eprintln!("No private key provided");
std::process::exit(1);
});
let private_key = SecretKey::from_slice(&key_bytes).unwrap_or_else(|_| {
eprintln!("Invalid private key");
std::process::exit(1);
});
let request_body = args.get_str("<request-body>");

// Create E2eApi instance
let api = ApiBuilder::new(our_id, secret)
.with_private_key_bytes(private_key.as_ref())
.and_then(|builder| builder.into_e2e())
.with_private_key(private_key)
.into_e2e()
.unwrap();

// Parse request body
Expand Down
7 changes: 5 additions & 2 deletions examples/send_e2e_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ async fn main() {
file: file_bytes,
thumbnail: thumbnail_bytes,
};
let (encrypted, key) = encrypt_file_data(&file_data);
let (encrypted, key) = etry!(encrypt_file_data(&file_data), "Could not encrypt file");

// Upload files to blob server
let file_blob_id = etry!(
Expand Down Expand Up @@ -126,7 +126,10 @@ async fn main() {
.rendering_type(rendering_type)
.build()
.expect("Could not build FileMessage");
let encrypted = api.encrypt_file_msg(&msg, &public_key.into());
let encrypted = etry!(
api.encrypt_file_msg(&msg, &public_key.into()),
"Could not encrypt file msg"
);

// Send
let msg_id = api.send(to, &encrypted, false).await;
Expand Down
26 changes: 18 additions & 8 deletions examples/send_e2e_image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@ async fn main() {
println!("Could not read file: {}", e);
process::exit(1);
});
let encrypted_image = api.encrypt_raw(&img_data, &recipient_key);
let encrypted_image = api
.encrypt_raw(&img_data, &recipient_key)
.unwrap_or_else(|_| {
println!("Could encrypt raw msg");
process::exit(1);
});

// Upload image to blob server
let blob_id = api
Expand All @@ -73,15 +78,20 @@ async fn main() {
});

// Create image message
let msg = api.encrypt_image_msg(
&blob_id,
img_data.len() as u32,
&encrypted_image.nonce,
&recipient_key,
);
let msg = api
.encrypt_image_msg(
&blob_id,
img_data.len() as u32,
&encrypted_image.nonce,
&recipient_key,
)
.unwrap_or_else(|e| {
println!("Could not encrypt image msg: {e}");
process::exit(1);
});

// Send
let msg_id = api.send(&to, &msg, false).await;
let msg_id = api.send(to, &msg, false).await;
match msg_id {
Ok(id) => println!("Sent. Message id is {}.", id),
Err(e) => println!("Could not send message: {}", e),
Expand Down
9 changes: 7 additions & 2 deletions examples/send_e2e_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,13 @@ async fn main() {
});

// Encrypt and send
let encrypted = api.encrypt_text_msg(&text, &public_key.into());
let msg_id = api.send(&to, &encrypted, false).await;
let encrypted = api
.encrypt_text_msg(&text, &public_key.into())
.unwrap_or_else(|e| {
println!("Could not encrypt text msg: {e}");
process::exit(1);
});
let msg_id = api.send(to, &encrypted, false).await;

match msg_id {
Ok(id) => println!("Sent. Message id is {}.", id),
Expand Down
30 changes: 20 additions & 10 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ use std::{
time::Duration,
};

use crypto_box::{PublicKey, SecretKey};
use crypto_secretbox::Nonce;
use data_encoding::HEXLOWER_PERMISSIVE;
use reqwest::Client;
use sodiumoxide::crypto::box_::PublicKey;

use crate::{
connection::{blob_download, blob_upload, send_e2e, send_simple, Recipient},
Expand All @@ -20,7 +21,7 @@ use crate::{
},
receive::IncomingMessage,
types::{BlobId, FileMessage, MessageType},
SecretKey, MSGAPI_URL,
MSGAPI_URL,
};

fn make_reqwest_client() -> Client {
Expand Down Expand Up @@ -173,7 +174,11 @@ impl E2eApi {
}

/// Encrypt a text message for the specified recipient public key.
pub fn encrypt_text_msg(&self, text: &str, recipient_key: &RecipientKey) -> EncryptedMessage {
pub fn encrypt_text_msg(
&self,
text: &str,
recipient_key: &RecipientKey,
) -> Result<EncryptedMessage, CryptoError> {
let data = text.as_bytes();
let msgtype = MessageType::Text;
encrypt(data, msgtype, &recipient_key.0, &self.private_key)
Expand All @@ -192,9 +197,9 @@ impl E2eApi {
&self,
blob_id: &BlobId,
img_size_bytes: u32,
image_data_nonce: &[u8; 24],
image_data_nonce: &Nonce,
recipient_key: &RecipientKey,
) -> EncryptedMessage {
) -> Result<EncryptedMessage, CryptoError> {
encrypt_image_msg(
blob_id,
img_size_bytes,
Expand All @@ -214,7 +219,7 @@ impl E2eApi {
&self,
msg: &FileMessage,
recipient_key: &RecipientKey,
) -> EncryptedMessage {
) -> Result<EncryptedMessage, CryptoError> {
encrypt_file_msg(msg, &recipient_key.0, &self.private_key)
}

Expand All @@ -233,12 +238,16 @@ impl E2eApi {
raw_data: &[u8],
msgtype: MessageType,
recipient_key: &RecipientKey,
) -> EncryptedMessage {
) -> Result<EncryptedMessage, CryptoError> {
encrypt(raw_data, msgtype, &recipient_key.0, &self.private_key)
}

/// Encrypt raw bytes for the specified recipient public key.
pub fn encrypt_raw(&self, raw_data: &[u8], recipient_key: &RecipientKey) -> EncryptedMessage {
pub fn encrypt_raw(
&self,
raw_data: &[u8],
recipient_key: &RecipientKey,
) -> Result<EncryptedMessage, CryptoError> {
encrypt_raw(raw_data, &recipient_key.0, &self.private_key)
}

Expand Down Expand Up @@ -507,8 +516,9 @@ impl ApiBuilder {

/// Set the private key from a byte slice. Only needed for E2e mode.
pub fn with_private_key_bytes(mut self, private_key: &[u8]) -> Result<Self, ApiBuilderError> {
let private_key = SecretKey::from_slice(private_key)
.ok_or_else(|| ApiBuilderError::InvalidKey("Invalid libsodium private key".into()))?;
let private_key = SecretKey::from_slice(private_key).map_err(|e| {
ApiBuilderError::InvalidKey(format!("Invalid libsodium private key: {e}"))
})?;
self.private_key = Some(private_key);
Ok(self)
}
Expand Down
6 changes: 2 additions & 4 deletions src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,13 +223,11 @@ pub(crate) async fn blob_download(
mod tests {
use super::*;

use std::iter::repeat;

use crate::{errors::ApiError, MSGAPI_URL};

#[tokio::test]
async fn test_simple_max_length_ok() {
let text: String = repeat("à").take(3500 / 2).collect();
let text: String = "à".repeat(3500 / 2);
let client = Client::new();
let result = send_simple(
&client,
Expand All @@ -247,7 +245,7 @@ mod tests {

#[tokio::test]
async fn test_simple_max_length_too_long() {
let mut text: String = repeat("à").take(3500 / 2).collect();
let mut text: String = "à".repeat(3500 / 2);
text.push('x');
let client = Client::new();
let result = send_simple(
Expand Down
Loading