Skip to content

Commit ec51a9a

Browse files
authored
update: Upgrade all dependencies to latest versions (#173)
* update: Upgrade all dependencies to latest versions - Upgrade 30+ crate dependencies to latest compatible versions - Sync bssh-russh fork with upstream russh 0.59.0 (from 0.56.0) - 21% throughput improvement from CryptoVec → bytes::Bytes migration - RSA Marvin attack mitigation, ml-kem replacement, aws-lc-rs security patch - Certificate-based SSH agent authentication support - Re-apply PTY output batch processing patch on new codebase - Migrate OpenTelemetry from 0.21 to 0.31 - Rewrite otel.rs for new SdkLoggerProvider, Resource::builder, LogRecord API - Adapt bssh code to russh 0.59 API changes - Handle::data/extended_data now takes impl Into<bytes::Bytes> - AgentIdentity comparison via public_key() - Add bytes crate as direct dependency - Keep rand at 0.8 in main crate (ssh-key requires rand_core 0.6) * fix: Add #[serial] to env var tests to prevent race conditions in CI
1 parent 8dc433d commit ec51a9a

53 files changed

Lines changed: 2969 additions & 1334 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 619 additions & 646 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,75 +17,76 @@ categories = ["command-line-utilities"]
1717
edition = "2021"
1818

1919
[dependencies]
20-
tokio = { version = "1.48.0", features = ["full"] }
20+
bytes = "1"
21+
tokio = { version = "1.50.0", features = ["full"] }
2122
# Use our internal russh fork with session loop fixes
2223
# - Development: uses local path (crates/bssh-russh)
2324
# - Publishing: uses crates.io version (path ignored)
24-
russh = { package = "bssh-russh", version = "0.56", path = "crates/bssh-russh" }
25+
russh = { package = "bssh-russh", version = "0.59", path = "crates/bssh-russh" }
2526
russh-sftp = "2.1.1"
26-
clap = { version = "4.5.53", features = ["derive", "env"] }
27-
anyhow = "1.0.100"
28-
thiserror = "2.0.17"
27+
clap = { version = "4.6.0", features = ["derive", "env"] }
28+
anyhow = "1.0.102"
29+
thiserror = "2.0.18"
2930
tracing = "0.1.43"
30-
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
31+
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
3132
serde = { version = "1.0.228", features = ["derive"] }
3233
serde_yaml = "0.9"
33-
futures = "0.3.31"
34+
futures = "0.3.32"
3435
async-trait = "0.1.89"
35-
indicatif = "0.18.3"
36+
indicatif = "0.18.4"
3637
rpassword = "7.4.0"
3738
directories = "6.0.0"
3839
dirs = "6.0"
39-
chrono = { version = "0.4.42", features = ["serde"] }
40+
chrono = { version = "0.4.44", features = ["serde"] }
4041
glob = "0.3.3"
41-
whoami = "2.0.1"
42-
owo-colors = "4.2.3"
42+
whoami = "2.1.1"
43+
owo-colors = "4.3.0"
4344
unicode-width = "0.2.2"
44-
terminal_size = "0.4.3"
45-
once_cell = "1.21.3"
45+
terminal_size = "0.4.4"
46+
once_cell = "1.21.4"
4647
zeroize = { version = "1.8.2", features = ["derive"] }
4748
secrecy = { version = "0.10.3", features = ["serde"] }
48-
rustyline = "17.0.2"
49+
rustyline = "18.0.0"
4950
crossterm = "0.29"
5051
ratatui = "0.30"
51-
regex = "1.12.2"
52+
regex = "1.12.3"
5253
lazy_static = "1.5"
53-
ctrlc = "3.5.1"
54-
signal-hook = "0.4.1"
55-
nix = { version = "0.30", features = ["fs", "poll", "process", "signal", "term"] }
54+
ctrlc = "3.5.2"
55+
signal-hook = "0.4.3"
56+
nix = { version = "0.31", features = ["fs", "poll", "process", "signal", "term"] }
5657
atty = "0.2.14"
5758
arrayvec = "0.7.6"
5859
smallvec = "1.15.1"
5960
lru = "0.16.2"
60-
uuid = { version = "1.19.0", features = ["v4"] }
61+
uuid = { version = "1.23.0", features = ["v4"] }
6162
fastrand = "2.3.0"
6263
tokio-util = "0.7.17"
6364
shell-words = "1.1.1"
6465
libc = "0.2"
65-
ipnetwork = "0.20"
66-
bcrypt = "0.16"
66+
ipnetwork = "0.21"
67+
bcrypt = "0.19"
6768
argon2 = "0.5"
6869
rand = "0.8"
6970
ssh-key = { version = "0.6", features = ["std"] }
7071
async-compression = { version = "0.4", features = ["tokio", "gzip"] }
7172
serde_json = "1.0"
72-
opentelemetry = "0.21"
73-
opentelemetry_sdk = { version = "0.21", features = ["rt-tokio", "logs"] }
74-
opentelemetry-otlp = { version = "0.14", features = ["grpc-tonic", "logs"] }
73+
opentelemetry = "0.31"
74+
opentelemetry_sdk = { version = "0.31", features = ["rt-tokio", "logs"] }
75+
opentelemetry-otlp = { version = "0.31", features = ["grpc-tonic", "logs"] }
7576
url = "2.5"
7677
tokio-rustls = "0.26"
7778
rustls-native-certs = "0.8"
7879

7980
[target.'cfg(target_os = "macos")'.dependencies]
80-
security-framework = "3.5.1"
81+
security-framework = "3.7.0"
8182

8283
[dev-dependencies]
83-
tempfile = "3.23.0"
84-
mockito = "1.7.1"
85-
once_cell = "1.21.3"
84+
tempfile = "3.27.0"
85+
mockito = "1.7.2"
86+
once_cell = "1.21.4"
8687
tokio-test = "0.4"
87-
serial_test = "3.2"
88-
insta = "1.44"
88+
serial_test = "3.4"
89+
insta = "1.47"
8990
criterion = { version = "0.8", features = ["html_reports"] }
9091
mockall = "0.14"
9192

benches/large_output_benchmark.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ use bssh::executor::{MultiNodeStreamManager, NodeStream};
2424
use bssh::node::Node;
2525
use bssh::ssh::tokio_client::CommandOutput;
2626
use bssh::ui::tui::app::TuiApp;
27+
use bytes::Bytes;
2728
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
2829
use ratatui::backend::TestBackend;
2930
use ratatui::Terminal;
30-
use russh::CryptoVec;
3131
use std::hint::black_box;
3232
use tokio::runtime::Runtime;
3333
use tokio::sync::mpsc;
@@ -65,7 +65,7 @@ fn bench_large_output_single_stream(c: &mut Criterion) {
6565

6666
// Send data in 32KB chunks (typical SSH packet size)
6767
let chunk_size = 32 * 1024;
68-
let chunk = CryptoVec::from(vec![b'x'; chunk_size.min(size)]);
68+
let chunk = Bytes::from(vec![b'x'; chunk_size.min(size)]);
6969
let num_chunks = size.div_ceil(chunk_size);
7070

7171
for _ in 0..num_chunks {
@@ -110,7 +110,7 @@ fn bench_rolling_buffer_overflow(c: &mut Criterion) {
110110

111111
// Send data in chunks to exceed buffer limit
112112
let chunk_size = 64 * 1024; // 64KB chunks
113-
let chunk = CryptoVec::from(vec![b'x'; chunk_size]);
113+
let chunk = Bytes::from(vec![b'x'; chunk_size]);
114114
let num_chunks = total_size / chunk_size;
115115

116116
for _ in 0..num_chunks {
@@ -162,7 +162,7 @@ fn bench_concurrent_multi_node(c: &mut Criterion) {
162162

163163
// Send data to all nodes
164164
let data_per_node = 100 * 1024; // 100KB per node
165-
let chunk = CryptoVec::from(vec![b'x'; 1024]);
165+
let chunk = Bytes::from(vec![b'x'; 1024]);
166166
let chunks_per_node = data_per_node / 1024;
167167

168168
for _ in 0..chunks_per_node {
@@ -214,7 +214,7 @@ fn bench_poll_all_throughput(c: &mut Criterion) {
214214
senders.push(tx);
215215
}
216216

217-
let chunk = CryptoVec::from(vec![b'x'; chunk_size]);
217+
let chunk = Bytes::from(vec![b'x'; chunk_size]);
218218

219219
// Send one chunk to each node and poll
220220
for tx in &senders {
@@ -304,7 +304,7 @@ fn bench_tui_render_detail(c: &mut Criterion) {
304304
}
305305

306306
rt.block_on(async {
307-
tx.send(CommandOutput::StdOut(CryptoVec::from(
307+
tx.send(CommandOutput::StdOut(Bytes::from(
308308
output.as_bytes().to_vec(),
309309
)))
310310
.await

crates/bssh-russh/Cargo.toml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "bssh-russh"
3-
version = "0.56.0"
3+
version = "0.59.0"
44
authors = ["Jeongkyu Shin <inureyes@gmail.com>"]
55
description = "Temporary fork of russh with high-frequency PTY output fix (Handle::data from spawned tasks)"
66
documentation = "https://docs.rs/bssh-russh"
@@ -21,11 +21,12 @@ des = ["dep:des"]
2121
dsa = ["ssh-key/dsa"]
2222
ring = ["dep:ring"]
2323
rsa = ["dep:rsa", "dep:pkcs1", "ssh-key/rsa", "ssh-key/rsa-sha1"]
24+
serde = ["ssh-key/serde"]
2425

2526
[dependencies]
2627
aes = "0.8"
2728
async-trait = { version = "0.1.50", optional = true }
28-
aws-lc-rs = { version = "1.13.1", optional = true }
29+
aws-lc-rs = { version = "1.16.2", optional = true }
2930
bitflags = "2.0"
3031
block-padding = { version = "0.3", features = ["std"] }
3132
byteorder = "1.4"
@@ -46,12 +47,12 @@ flate2 = { version = "1.0.15", optional = true }
4647
futures = "0.3"
4748
generic-array = { version = "1.3.3", features = ["compat-0_14"] }
4849
getrandom = { version = "0.2.15", features = ["js"] }
49-
hex-literal = "0.4"
50+
hex-literal = "1"
5051
hmac = "0.12"
5152
inout = { version = "0.1", features = ["std"] }
52-
libcrux-ml-kem = "0.0.4"
5353
log = "0.4"
5454
md5 = "0.7"
55+
ml-kem = "0.2.3"
5556
num-bigint = { version = "0.4.2", features = ["rand"] }
5657
p256 = { version = "0.13", features = ["ecdh"] }
5758
p384 = { version = "0.13", features = ["ecdh"] }
@@ -60,8 +61,8 @@ pbkdf2 = "0.12"
6061
pkcs1 = { version = "0.8.0-rc.4", optional = true }
6162
pkcs5 = "0.7"
6263
pkcs8 = { version = "0.10", features = ["pkcs5", "encryption", "std"] }
63-
rand_core = { version = "0.6.4", features = ["getrandom", "std"] }
64-
rand = "0.8"
64+
rand_core = { version = "=0.10.0-rc-3" }
65+
rand = { version = "0.9", features = ["thread_rng"] }
6566
ring = { version = "0.17.14", optional = true }
6667
rsa = { version = "0.10.0-rc.10", optional = true }
6768
sec1 = { version = "0.7", features = ["pkcs8", "der"] }
@@ -71,15 +72,14 @@ signature = "2.2"
7172
spki = "0.7"
7273
ssh-encoding = { version = "0.2", features = ["bytes"] }
7374
subtle = "2.4"
74-
thiserror = "1.0.30"
75-
tokio = { version = "1.48.0", features = ["io-util", "sync", "time", "rt-multi-thread", "net"] }
75+
thiserror = "2.0.18"
76+
tokio = { version = "1.50.0", features = ["io-util", "sync", "time", "rt-multi-thread", "net"] }
7677
typenum = "1.17"
7778
yasna = { version = "0.5.0", features = ["bit-vec", "num-bigint"], optional = true }
7879
zeroize = "1.7"
79-
home = "0.5"
8080

8181
# Public russh crates (no modifications needed)
82-
russh-cryptovec = { version = "0.52.0", features = ["ssh-encoding"] }
82+
russh-cryptovec = { version = "0.59.0", features = ["ssh-encoding"] }
8383
russh-util = "0.52.0"
8484

8585
# Use the forked ssh-key from russh

crates/bssh-russh/patches/handle-data-fix.patch

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
--- a/src/server/session.rs 2026-01-23 18:47:48
2-
+++ b/src/server/session.rs 2026-01-24 03:08:34
1+
--- /tmp/russh-upstream-compare/russh/src/server/session.rs 2026-04-03 13:17:42
2+
+++ /Users/inureyes/Development/backend.ai/bssh/crates/bssh-russh/src/server/session.rs 2026-04-03 13:20:54
33
@@ -7,7 +7,7 @@
44
use log::debug;
55
use negotiation::parse_kex_algo_list;
@@ -9,12 +9,7 @@
99
use tokio::sync::oneshot;
1010

1111
use super::*;
12-
@@ -502,10 +502,141 @@
13-
pin!(reading);
14-
let mut is_reading = None;
15-
16-
+
17-
#[allow(clippy::panic)] // false positive in macro
12+
@@ -513,6 +513,136 @@
1813
while !self.common.disconnected {
1914
self.common.received_data = false;
2015
let mut sent_keepalive = false;

crates/bssh-russh/src/auth.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ use ssh_key::{Certificate, HashAlg, PrivateKey};
2222
use thiserror::Error;
2323
use tokio::io::{AsyncRead, AsyncWrite};
2424

25-
use crate::CryptoVec;
2625
use crate::helpers::NameList;
2726
use crate::keys::PrivateKeyWithHashAlg;
27+
use crate::keys::agent::AgentIdentity;
2828

2929
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3030
pub enum MethodKind {
@@ -157,12 +157,12 @@ impl AuthResult {
157157
pub trait Signer: Sized {
158158
type Error: From<crate::SendError>;
159159

160-
fn auth_publickey_sign(
160+
fn auth_sign(
161161
&mut self,
162-
key: &ssh_key::PublicKey,
162+
key: &AgentIdentity,
163163
hash_alg: Option<HashAlg>,
164-
to_sign: CryptoVec,
165-
) -> impl Future<Output = Result<CryptoVec, Self::Error>> + Send;
164+
to_sign: Vec<u8>,
165+
) -> impl Future<Output = Result<Vec<u8>, Self::Error>> + Send;
166166
}
167167

168168
#[derive(Debug, Error)]
@@ -180,12 +180,12 @@ impl<R: AsyncRead + AsyncWrite + Unpin + Send + 'static> Signer
180180
type Error = AgentAuthError;
181181

182182
#[allow(clippy::manual_async_fn)]
183-
fn auth_publickey_sign(
183+
fn auth_sign(
184184
&mut self,
185-
key: &ssh_key::PublicKey,
185+
key: &AgentIdentity,
186186
hash_alg: Option<HashAlg>,
187-
to_sign: CryptoVec,
188-
) -> impl Future<Output = Result<CryptoVec, Self::Error>> {
187+
to_sign: Vec<u8>,
188+
) -> impl Future<Output = Result<Vec<u8>, Self::Error>> {
189189
async move {
190190
self.sign_request(key, hash_alg, to_sign)
191191
.await
@@ -212,6 +212,12 @@ pub enum Method {
212212
key: ssh_key::PublicKey,
213213
hash_alg: Option<HashAlg>,
214214
},
215+
/// Certificate-based authentication using an external signer (e.g., SSH agent).
216+
/// The certificate is sent to the server, but signing is delegated to the signer.
217+
FutureCertificate {
218+
cert: Certificate,
219+
hash_alg: Option<HashAlg>,
220+
},
215221
KeyboardInteractive {
216222
submethods: String,
217223
},
@@ -235,9 +241,9 @@ pub enum CurrentRequest {
235241
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
236242
PublicKey {
237243
#[allow(dead_code)]
238-
key: CryptoVec,
244+
key: Vec<u8>,
239245
#[allow(dead_code)]
240-
algo: CryptoVec,
246+
algo: Vec<u8>,
241247
sent_pk_ok: bool,
242248
},
243249
KeyboardInteractive {

crates/bssh-russh/src/channels/io/tx.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ use tokio::sync::mpsc::error::SendError;
1313
use tokio::sync::mpsc::{self, OwnedPermit};
1414
use tokio::sync::{Mutex, Notify, OwnedMutexGuard};
1515

16+
use bytes::Bytes;
17+
1618
use super::ChannelMsg;
17-
use crate::{ChannelId, CryptoVec};
19+
use crate::ChannelId;
1820

1921
type BoxedThreadsafeFuture<T> = Pin<Box<dyn Sync + Send + std::future::Future<Output = T>>>;
2022
type OwnedPermitFuture<S> =
@@ -112,10 +114,8 @@ where
112114
) -> Poll<(ChannelMsg, NonZeroUsize)> {
113115
let writable = ready!(self.poll_writable(cx, buf.len()));
114116

115-
let mut data = CryptoVec::new_zeroed(writable.into());
116117
#[allow(clippy::indexing_slicing)] // Clamped to maximum `buf.len()` with `.poll_writable`
117-
data.copy_from_slice(&buf[..writable.into()]);
118-
data.resize(writable.into());
118+
let data = Bytes::copy_from_slice(&buf[..writable.into()]);
119119

120120
let msg = match self.ext {
121121
None => ChannelMsg::Data { data },

crates/bssh-russh/src/channels/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use std::sync::Arc;
22

3+
use bytes::Bytes;
34
use tokio::io::{AsyncRead, AsyncWrite};
45
use tokio::sync::mpsc::{Receiver, Sender};
56
use tokio::sync::{Mutex, Notify};
67

7-
use crate::{ChannelId, ChannelOpenFailure, CryptoVec, Error, Pty, Sig};
8+
use crate::{ChannelId, ChannelOpenFailure, Error, Pty, Sig};
89

910
pub mod io;
1011

@@ -24,10 +25,10 @@ pub enum ChannelMsg {
2425
window_size: u32,
2526
},
2627
Data {
27-
data: CryptoVec,
28+
data: Bytes,
2829
},
2930
ExtendedData {
30-
data: CryptoVec,
31+
data: Bytes,
3132
ext: u32,
3233
},
3334
Eof,

crates/bssh-russh/src/cipher/benchmark.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
#![allow(clippy::unwrap_used)]
22
use criterion::*;
3-
use rand::RngCore;
3+
use rand::TryRngCore;
4+
use std::hint;
45

56
pub fn bench(c: &mut Criterion) {
6-
let mut rand_generator = black_box(rand::rngs::OsRng {});
7+
let mut rand_generator = hint::black_box(rand::rngs::OsRng {});
78

8-
let mut packet_length = black_box(vec![0u8; 4]);
9+
let mut packet_length = hint::black_box(vec![0u8; 4]);
910

1011
for cipher_name in [super::CHACHA20_POLY1305, super::AES_256_GCM] {
1112
let cipher = super::CIPHERS.get(&cipher_name).unwrap();
@@ -26,7 +27,7 @@ pub fn bench(c: &mut Criterion) {
2627
group.bench_function(format!("Block size: {size}"), |b| {
2728
b.iter_with_setup(
2829
|| {
29-
let mut in_out = black_box(vec![0u8; size]);
30+
let mut in_out = hint::black_box(vec![0u8; size]);
3031
rand_generator.try_fill_bytes(&mut in_out).unwrap();
3132
rand_generator.try_fill_bytes(&mut packet_length).unwrap();
3233
in_out

0 commit comments

Comments
 (0)