Skip to content

Commit c41d4f6

Browse files
EpicEricEugeny
andauthored
Use aws-lc-rs/ring implementation for AEAD ciphers (#535)
Co-authored-by: Eugene <[email protected]>
1 parent 052109c commit c41d4f6

File tree

8 files changed

+143
-212
lines changed

8 files changed

+143
-212
lines changed

.github/workflows/rust.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ jobs:
2828
steps:
2929
- uses: actions/checkout@v2
3030

31+
- name: install nasm
32+
run: |
33+
choco install nasm
34+
echo "C:\Program Files\NASM" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
35+
3136
- name: Build (no features enabled)
3237
run: cargo build --verbose
3338

@@ -45,8 +50,8 @@ jobs:
4550
rustup toolchain add 1.81.0
4651
rustup target add --toolchain 1.81.0 wasm32-wasip1
4752
48-
- name: Build (all features enabled)
49-
run: cargo +1.81.0 build --verbose --target wasm32-wasip1 -p russh
53+
- name: Build (WASM-compatible features)
54+
run: cargo +1.81.0 build --verbose --target wasm32-wasip1 --no-default-features --features flate2,ring -p russh
5055

5156
Formatting:
5257
runs-on: ubuntu-24.04

russh/Cargo.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,26 @@ version = "0.52.1"
1313
rust-version = "1.75"
1414

1515
[features]
16-
default = ["flate2"]
16+
default = ["flate2", "aws-lc-rs"]
17+
aws-lc-rs = ["dep:aws-lc-rs"]
1718
async-trait = ["dep:async-trait"]
1819
legacy-ed25519-pkcs8-parser = ["yasna"]
1920
# Danger: 3DES cipher is insecure.
2021
des = ["dep:des"]
2122
# Danger: DSA algorithm is insecure.
2223
dsa = ["ssh-key/dsa"]
24+
ring = ["dep:ring"]
2325
_bench = ["dep:criterion"]
2426

2527
[dependencies]
26-
aes-gcm = "0.10"
2728
aes.workspace = true
2829
async-trait = { workspace = true, optional = true }
30+
aws-lc-rs = { version = "1.13.1", optional = true }
2931
bitflags = "2.0"
3032
block-padding = { version = "0.3", features = ["std"] }
3133
byteorder.workspace = true
3234
bytes.workspace = true
3335
cbc = { version = "0.1" }
34-
chacha20 = "0.9"
3536
ctr = "0.9"
3637
curve25519-dalek = "4.1.3"
3738
data-encoding = "2.3"
@@ -62,9 +63,9 @@ pbkdf2 = "0.12"
6263
pkcs1 = "0.7"
6364
pkcs5 = "0.7"
6465
pkcs8 = { version = "0.10", features = ["pkcs5", "encryption"] }
65-
poly1305 = "0.8"
6666
rand_core = { version = "0.6.4", features = ["getrandom", "std"] }
6767
rand.workspace = true
68+
ring = { version = "0.17.14", optional = true }
6869
rsa.workspace = true
6970
russh-cryptovec = { version = "0.52.0", path = "../cryptovec", features = [
7071
"ssh-encoding",

russh/src/cipher/benchmark.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ pub fn bench(c: &mut Criterion) {
2121
let mut group = c.benchmark_group(format!("Cipher: {}", cipher_name.0));
2222
for size in [100usize, 1000, 10000] {
2323
let iterations = 10000 / size;
24-
let mut tag = black_box(vec![0u8; sk.tag_len()]);
2524

2625
group.throughput(Throughput::Bytes(size as u64));
2726
group.bench_function(format!("Block size: {size}"), |b| {
@@ -34,8 +33,10 @@ pub fn bench(c: &mut Criterion) {
3433
},
3534
|mut in_out| {
3635
for _ in 0..iterations {
37-
sk.seal(0, &mut in_out, &mut tag);
38-
ok.open(0, &mut in_out, &tag).unwrap();
36+
let len = in_out.len();
37+
let (data, tag) = in_out.split_at_mut(len - sk.tag_len());
38+
sk.seal(0, data, tag);
39+
ok.open(0, &mut in_out).unwrap();
3940
}
4041
},
4142
);

russh/src/cipher/block.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,10 @@ impl<C: BlockStreamCipher + KeySizeUser + IvSizeUser> super::OpeningKey for Open
122122
fn open<'a>(
123123
&mut self,
124124
sequence_number: u32,
125-
ciphertext_in_plaintext_out: &'a mut [u8],
126-
tag: &[u8],
125+
ciphertext_and_tag: &'a mut [u8],
127126
) -> Result<&'a [u8], Error> {
127+
let ciphertext_len = ciphertext_and_tag.len() - self.tag_len();
128+
let (ciphertext_in_plaintext_out, tag) = ciphertext_and_tag.split_at_mut(ciphertext_len);
128129
if self.mac.is_etm() {
129130
if !self
130131
.mac

russh/src/cipher/chacha20poly1305.rs

Lines changed: 39 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -15,124 +15,85 @@
1515

1616
// http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEAD
1717

18-
use std::convert::TryInto;
19-
20-
use aes::cipher::{BlockSizeUser, StreamCipherSeek};
21-
use byteorder::{BigEndian, ByteOrder};
22-
use chacha20::cipher::{KeyInit, KeyIvInit, StreamCipher};
23-
use chacha20::{ChaCha20Legacy, ChaCha20LegacyCore};
24-
use generic_array::typenum::{Unsigned, U16, U32, U8};
25-
use generic_array::GenericArray;
26-
use poly1305::Poly1305;
27-
use subtle::ConstantTimeEq;
18+
#[cfg(feature = "aws-lc-rs")]
19+
use aws_lc_rs::aead::chacha20_poly1305_openssh;
20+
#[cfg(all(not(feature = "aws-lc-rs"), feature = "ring"))]
21+
use ring::aead::chacha20_poly1305_openssh;
2822

2923
use super::super::Error;
30-
use crate::cipher::PACKET_LENGTH_LEN;
3124
use crate::mac::MacAlgorithm;
3225

3326
pub struct SshChacha20Poly1305Cipher {}
3427

35-
type KeyLength = U32;
36-
type NonceLength = U8;
37-
type TagLength = U16;
38-
type Key = GenericArray<u8, KeyLength>;
39-
type Nonce = GenericArray<u8, NonceLength>;
40-
4128
impl super::Cipher for SshChacha20Poly1305Cipher {
4229
fn key_len(&self) -> usize {
43-
KeyLength::to_usize() * 2
30+
chacha20_poly1305_openssh::KEY_LEN
4431
}
4532

46-
#[allow(clippy::indexing_slicing)] // length checked
4733
fn make_opening_key(
4834
&self,
4935
k: &[u8],
5036
_: &[u8],
5137
_: &[u8],
5238
_: &dyn MacAlgorithm,
5339
) -> Box<dyn super::OpeningKey + Send> {
54-
let mut k1 = Key::default();
55-
let mut k2 = Key::default();
56-
k1.clone_from_slice(&k[KeyLength::to_usize()..]);
57-
k2.clone_from_slice(&k[..KeyLength::to_usize()]);
58-
Box::new(OpeningKey { k1, k2 })
40+
Box::new(OpeningKey(chacha20_poly1305_openssh::OpeningKey::new(
41+
#[allow(clippy::unwrap_used)]
42+
k.try_into().unwrap(),
43+
)))
5944
}
6045

61-
#[allow(clippy::indexing_slicing)] // length checked
6246
fn make_sealing_key(
6347
&self,
6448
k: &[u8],
6549
_: &[u8],
6650
_: &[u8],
6751
_: &dyn MacAlgorithm,
6852
) -> Box<dyn super::SealingKey + Send> {
69-
let mut k1 = Key::default();
70-
let mut k2 = Key::default();
71-
k1.clone_from_slice(&k[KeyLength::to_usize()..]);
72-
k2.clone_from_slice(&k[..KeyLength::to_usize()]);
73-
Box::new(SealingKey { k1, k2 })
53+
Box::new(SealingKey(chacha20_poly1305_openssh::SealingKey::new(
54+
#[allow(clippy::unwrap_used)]
55+
k.try_into().unwrap(),
56+
)))
7457
}
7558
}
7659

77-
pub struct OpeningKey {
78-
k1: Key,
79-
k2: Key,
80-
}
81-
82-
pub struct SealingKey {
83-
k1: Key,
84-
k2: Key,
85-
}
60+
pub struct OpeningKey(chacha20_poly1305_openssh::OpeningKey);
8661

87-
#[allow(clippy::indexing_slicing)] // length checked
88-
fn make_counter(sequence_number: u32) -> Nonce {
89-
let mut nonce = Nonce::default();
90-
let i0 = NonceLength::to_usize() - 4;
91-
BigEndian::write_u32(&mut nonce[i0..], sequence_number);
92-
nonce
93-
}
62+
pub struct SealingKey(chacha20_poly1305_openssh::SealingKey);
9463

9564
impl super::OpeningKey for OpeningKey {
9665
fn decrypt_packet_length(
9766
&self,
9867
sequence_number: u32,
9968
encrypted_packet_length: &[u8],
10069
) -> [u8; 4] {
101-
// Fine because of self.packet_length_to_read_for_block_length()
102-
#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
103-
let mut encrypted_packet_length: [u8; 4] = encrypted_packet_length.try_into().unwrap();
104-
105-
let nonce = make_counter(sequence_number);
106-
let mut cipher = ChaCha20Legacy::new(&self.k1, &nonce);
107-
cipher.apply_keystream(&mut encrypted_packet_length);
108-
109-
encrypted_packet_length
70+
self.0.decrypt_packet_length(
71+
sequence_number,
72+
#[allow(clippy::unwrap_used)]
73+
encrypted_packet_length.try_into().unwrap(),
74+
)
11075
}
11176

11277
fn tag_len(&self) -> usize {
113-
TagLength::to_usize()
78+
chacha20_poly1305_openssh::TAG_LEN
11479
}
11580

116-
#[allow(clippy::indexing_slicing)] // lengths checked
11781
fn open<'a>(
11882
&mut self,
11983
sequence_number: u32,
120-
ciphertext_in_plaintext_out: &'a mut [u8],
121-
tag: &[u8],
84+
ciphertext_and_tag: &'a mut [u8],
12285
) -> Result<&'a [u8], Error> {
123-
let nonce = make_counter(sequence_number);
124-
let expected_tag = compute_poly1305(&nonce, &self.k2, ciphertext_in_plaintext_out);
125-
126-
if !bool::from(expected_tag.ct_eq(tag)) {
127-
return Err(Error::DecryptionError);
128-
}
129-
130-
let mut cipher = ChaCha20Legacy::new(&self.k2, &nonce);
131-
132-
cipher.seek(<ChaCha20LegacyCore as BlockSizeUser>::BlockSize::to_usize());
133-
cipher.apply_keystream(&mut ciphertext_in_plaintext_out[PACKET_LENGTH_LEN..]);
134-
135-
Ok(&ciphertext_in_plaintext_out[PACKET_LENGTH_LEN..])
86+
let ciphertext_len = ciphertext_and_tag.len() - self.tag_len();
87+
let (ciphertext_in_plaintext_out, tag) = ciphertext_and_tag.split_at_mut(ciphertext_len);
88+
89+
self.0
90+
.open_in_place(
91+
sequence_number,
92+
ciphertext_in_plaintext_out,
93+
#[allow(clippy::unwrap_used)]
94+
&tag.try_into().unwrap(),
95+
)
96+
.map_err(|_| Error::DecryptionError)
13697
}
13798
}
13899

@@ -163,7 +124,7 @@ impl super::SealingKey for SealingKey {
163124
}
164125

165126
fn tag_len(&self) -> usize {
166-
TagLength::to_usize()
127+
chacha20_poly1305_openssh::TAG_LEN
167128
}
168129

169130
fn seal(
@@ -172,31 +133,11 @@ impl super::SealingKey for SealingKey {
172133
plaintext_in_ciphertext_out: &mut [u8],
173134
tag: &mut [u8],
174135
) {
175-
let nonce = make_counter(sequence_number);
176-
177-
let mut cipher = ChaCha20Legacy::new(&self.k1, &nonce);
178-
#[allow(clippy::indexing_slicing)] // length checked
179-
cipher.apply_keystream(&mut plaintext_in_ciphertext_out[..PACKET_LENGTH_LEN]);
180-
181-
// --
182-
let mut cipher = ChaCha20Legacy::new(&self.k2, &nonce);
183-
184-
cipher.seek(<ChaCha20LegacyCore as BlockSizeUser>::BlockSize::to_usize());
185-
#[allow(clippy::indexing_slicing, clippy::unwrap_used)]
186-
cipher.apply_keystream(&mut plaintext_in_ciphertext_out[PACKET_LENGTH_LEN..]);
187-
188-
// --
189-
190-
tag.copy_from_slice(
191-
compute_poly1305(&nonce, &self.k2, plaintext_in_ciphertext_out).as_slice(),
136+
self.0.seal_in_place(
137+
sequence_number,
138+
plaintext_in_ciphertext_out,
139+
#[allow(clippy::unwrap_used)]
140+
tag.try_into().unwrap(),
192141
);
193142
}
194143
}
195-
196-
fn compute_poly1305(nonce: &Nonce, key: &Key, data: &[u8]) -> poly1305::Tag {
197-
let mut cipher = ChaCha20Legacy::new(key, nonce);
198-
let mut poly_key = GenericArray::<u8, U32>::default();
199-
cipher.apply_keystream(&mut poly_key);
200-
201-
Poly1305::new(&poly_key).compute_unpadded(data)
202-
}

russh/src/cipher/clear.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,10 @@ impl super::OpeningKey for Key {
6363
fn open<'a>(
6464
&mut self,
6565
_seqn: u32,
66-
ciphertext_in_plaintext_out: &'a mut [u8],
67-
tag: &[u8],
66+
ciphertext_and_tag: &'a mut [u8],
6867
) -> Result<&'a [u8], Error> {
69-
debug_assert_eq!(tag.len(), 0); // self.tag_len());
7068
#[allow(clippy::indexing_slicing)] // length known
71-
Ok(&ciphertext_in_plaintext_out[4..])
69+
Ok(&ciphertext_and_tag[4..])
7270
}
7371
}
7472

0 commit comments

Comments
 (0)