Skip to content

Commit ce6cd79

Browse files
EugenyNathy-bajo
andauthored
Use ssh-key instead of local key handling (#368)
Co-authored-by: Nathy-bajo <[email protected]>
1 parent e7a9ee9 commit ce6cd79

Some content is hidden

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

42 files changed

+891
-2532
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
target
55
Cargo.lock
66
.cargo-ok
7+
ca-test*

Cargo.toml

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
[workspace]
2-
members = ["russh-keys", "russh", "russh-config", "cryptovec", "pageant", "russh-util"]
2+
members = [
3+
"russh-keys",
4+
"russh",
5+
"russh-config",
6+
"cryptovec",
7+
"pageant",
8+
"russh-util",
9+
]
310
resolver = "2"
411

512
[patch.crates-io]
@@ -16,12 +23,19 @@ digest = "0.10"
1623
futures = "0.3"
1724
hmac = "0.12"
1825
log = "0.4"
19-
openssl = { version = "0.10" }
2026
rand = "0.8"
2127
sha1 = { version = "0.10", features = ["oid"] }
2228
sha2 = { version = "0.10", features = ["oid"] }
29+
signature = "2.2"
2330
ssh-encoding = "0.2"
24-
ssh-key = { version = "0.6", features = ["ed25519", "rsa", "encryption"] }
31+
ssh-key = { version = "0.6", features = [
32+
"ed25519",
33+
"rsa",
34+
"p256",
35+
"p384",
36+
"p521",
37+
"encryption",
38+
] }
2539
thiserror = "1.0"
2640
tokio = { version = "1.17.0" }
2741
tokio-stream = { version = "0.1", features = ["net", "sync"] }

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,11 @@ This is a fork of [Thrussh](https://nest.pijul.com/pijul/thrussh) by Pierre-Éti
5757
* `publickey`
5858
* `keyboard-interactive`
5959
* `none`
60-
* OpenSSH certificates (client only ✨)
60+
* OpenSSH certificates
6161
* Dependency updates
6262
* OpenSSH keepalive request handling ✨
6363
* OpenSSH agent forwarding channels ✨
6464
* OpenSSH `server-sig-algs` extension ✨
65-
* `openssl` dependency is optional ✨
6665

6766
## Safety
6867

russh-keys/Cargo.toml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ inout = { version = "0.1", features = ["std"] }
3131
log = { workspace = true }
3232
md5 = "0.7"
3333
num-integer = "0.1"
34-
openssl = { workspace = true, optional = true }
3534
p256 = "0.13"
3635
p384 = "0.13"
3736
p521 = "0.13"
@@ -48,6 +47,7 @@ sec1 = { version = "0.7", features = ["pkcs8"] }
4847
serde = { version = "1.0", features = ["derive"] }
4948
sha1 = { workspace = true }
5049
sha2 = { workspace = true }
50+
signature = { workspace = true }
5151
spki = "0.7"
5252
ssh-encoding = { workspace = true }
5353
ssh-key = { workspace = true }
@@ -62,7 +62,6 @@ tokio = { workspace = true, features = [
6262
] }
6363

6464
[features]
65-
vendored-openssl = ["openssl", "openssl/vendored"]
6665
legacy-ed25519-pkcs8-parser = ["yasna"]
6766

6867
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
@@ -83,6 +82,3 @@ pageant = { version = "0.0.1-beta.3", path = "../pageant" }
8382
env_logger = "0.11"
8483
tempdir = "0.3"
8584
tokio = { workspace = true, features = ["test-util", "macros", "process"] }
86-
87-
[package.metadata.docs.rs]
88-
features = ["openssl"]

russh-keys/src/agent/client.rs

Lines changed: 58 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
use std::convert::TryFrom;
1+
use core::str;
22

33
use byteorder::{BigEndian, ByteOrder};
44
use log::debug;
55
use russh_cryptovec::CryptoVec;
6+
use ssh_key::{Algorithm, HashAlg, PrivateKey, PublicKey, Signature};
67
use tokio;
78
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
89

910
use super::{msg, Constraint};
1011
use crate::encoding::{Encoding, Reader};
11-
use crate::key::{PublicKey, SignatureHash};
12-
use crate::{key, protocol, Error, PublicKeyBase64};
12+
use crate::helpers::EncodedExt;
13+
use crate::{key, Error};
1314

1415
pub trait AgentStream: AsyncRead + AsyncWrite {}
1516

@@ -142,7 +143,7 @@ impl<S: AgentStream + Unpin> AgentClient<S> {
142143
/// constraints to apply when using the key to sign.
143144
pub async fn add_identity(
144145
&mut self,
145-
key: &key::KeyPair,
146+
key: &PrivateKey,
146147
constraints: &[Constraint],
147148
) -> Result<(), Error> {
148149
// See IETF draft-miller-ssh-agent-13, section 3.2 for format.
@@ -154,30 +155,10 @@ impl<S: AgentStream + Unpin> AgentClient<S> {
154155
} else {
155156
self.buf.push(msg::ADD_ID_CONSTRAINED)
156157
}
157-
match *key {
158-
key::KeyPair::Ed25519(ref pair) => {
159-
self.buf.extend_ssh_string(b"ssh-ed25519");
160-
self.buf.extend_ssh_string(pair.verifying_key().as_bytes());
161-
self.buf.push_u32_be(64);
162-
self.buf.extend(pair.to_bytes().as_slice());
163-
self.buf.extend(pair.verifying_key().as_bytes());
164-
self.buf.extend_ssh_string(b"");
165-
}
166-
#[allow(clippy::unwrap_used)] // key is known to be private
167-
key::KeyPair::RSA { ref key, .. } => {
168-
self.buf.extend_ssh_string(b"ssh-rsa");
169-
self.buf
170-
.extend_ssh(&protocol::RsaPrivateKey::try_from(key)?);
171-
}
172-
key::KeyPair::EC { ref key } => {
173-
self.buf.extend_ssh_string(key.algorithm().as_bytes());
174-
self.buf.extend_ssh_string(key.ident().as_bytes());
175-
self.buf
176-
.extend_ssh_string(&key.to_public_key().to_sec1_bytes());
177-
self.buf.extend_ssh_mpint(&key.to_secret_bytes());
178-
self.buf.extend_ssh_string(b""); // comment
179-
}
180-
}
158+
159+
self.buf.extend(key.key_data().encoded()?.as_slice());
160+
self.buf.extend_ssh_string(&[]); // comment field
161+
181162
if !constraints.is_empty() {
182163
for cons in constraints {
183164
match *cons {
@@ -292,66 +273,54 @@ impl<S: AgentStream + Unpin> AgentClient<S> {
292273
for _ in 0..n {
293274
let key_blob = r.read_string()?;
294275
let _comment = r.read_string()?;
295-
keys.push(key::parse_public_key(
296-
key_blob,
297-
Some(SignatureHash::SHA2_512),
298-
)?);
276+
keys.push(key::parse_public_key(key_blob)?);
299277
}
300278
}
301279

302280
Ok(keys)
303281
}
304282

305283
/// Ask the agent to sign the supplied piece of data.
306-
pub fn sign_request(
307-
mut self,
308-
public: &key::PublicKey,
284+
pub async fn sign_request(
285+
&mut self,
286+
public: &PublicKey,
309287
mut data: CryptoVec,
310-
) -> impl futures::Future<Output = (Self, Result<CryptoVec, Error>)> {
288+
) -> Result<CryptoVec, Error> {
311289
debug!("sign_request: {:?}", data);
312-
let hash = self.prepare_sign_request(public, &data);
313-
314-
async move {
315-
if let Err(e) = hash {
316-
return (self, Err(e));
317-
}
290+
let hash = self.prepare_sign_request(public, &data)?;
318291

319-
let resp = self.read_response().await;
320-
debug!("resp = {:?}", &self.buf[..]);
321-
if let Err(e) = resp {
322-
return (self, Err(e));
323-
}
292+
self.read_response().await?;
324293

325-
#[allow(clippy::indexing_slicing, clippy::unwrap_used)]
326-
// length is checked, hash already checked
327-
if !self.buf.is_empty() && self.buf[0] == msg::SIGN_RESPONSE {
328-
let resp = self.write_signature(hash.unwrap(), &mut data);
329-
if let Err(e) = resp {
330-
return (self, Err(e));
331-
}
332-
(self, Ok(data))
333-
} else if self.buf.first() == Some(&msg::FAILURE) {
334-
(self, Err(Error::AgentFailure))
335-
} else {
336-
debug!("self.buf = {:?}", &self.buf[..]);
337-
(self, Ok(data))
338-
}
294+
if self.buf.first() == Some(&msg::SIGN_RESPONSE) {
295+
self.write_signature(hash, &mut data)?;
296+
Ok(data)
297+
} else if self.buf.first() == Some(&msg::FAILURE) {
298+
Err(Error::AgentFailure)
299+
} else {
300+
debug!("self.buf = {:?}", &self.buf[..]);
301+
Ok(data)
339302
}
340303
}
341304

342-
fn prepare_sign_request(&mut self, public: &key::PublicKey, data: &[u8]) -> Result<u32, Error> {
305+
fn prepare_sign_request(
306+
&mut self,
307+
public: &ssh_key::PublicKey,
308+
data: &[u8],
309+
) -> Result<u32, Error> {
343310
self.buf.clear();
344311
self.buf.resize(4);
345312
self.buf.push(msg::SIGN_REQUEST);
346313
key_blob(public, &mut self.buf)?;
347314
self.buf.extend_ssh_string(data);
348315
debug!("public = {:?}", public);
349-
let hash = match public {
350-
PublicKey::RSA { hash, .. } => match hash {
351-
SignatureHash::SHA2_256 => 2,
352-
SignatureHash::SHA2_512 => 4,
353-
SignatureHash::SHA1 => 0,
354-
},
316+
let hash = match public.algorithm() {
317+
Algorithm::Rsa {
318+
hash: Some(HashAlg::Sha256),
319+
} => 2,
320+
Algorithm::Rsa {
321+
hash: Some(HashAlg::Sha512),
322+
} => 4,
323+
Algorithm::Rsa { hash: None } => 0,
355324
_ => 0,
356325
};
357326
self.buf.push_u32_be(hash);
@@ -376,7 +345,7 @@ impl<S: AgentStream + Unpin> AgentClient<S> {
376345
/// Ask the agent to sign the supplied piece of data.
377346
pub fn sign_request_base64(
378347
mut self,
379-
public: &key::PublicKey,
348+
public: &ssh_key::PublicKey,
380349
data: &[u8],
381350
) -> impl futures::Future<Output = (Self, Result<String, Error>)> {
382351
debug!("sign_request: {:?}", data);
@@ -402,67 +371,32 @@ impl<S: AgentStream + Unpin> AgentClient<S> {
402371
}
403372

404373
/// Ask the agent to sign the supplied piece of data, and return a `Signature`.
405-
pub fn sign_request_signature(
406-
mut self,
407-
public: &key::PublicKey,
374+
pub async fn sign_request_signature(
375+
&mut self,
376+
public: &ssh_key::PublicKey,
408377
data: &[u8],
409-
) -> impl futures::Future<Output = (Self, Result<crate::signature::Signature, Error>)> {
378+
) -> Result<Signature, Error> {
410379
debug!("sign_request: {:?}", data);
411380

412-
let r = self.prepare_sign_request(public, data);
413-
414-
async move {
415-
if let Err(e) = r {
416-
return (self, Err(e));
417-
}
418-
419-
if let Err(e) = self.read_response().await {
420-
return (self, Err(e));
421-
}
381+
self.prepare_sign_request(public, data)?;
382+
self.read_response().await?;
422383

423-
#[allow(clippy::indexing_slicing)] // length is checked
424-
if !self.buf.is_empty() && self.buf[0] == msg::SIGN_RESPONSE {
425-
let as_sig = |buf: &CryptoVec| -> Result<crate::signature::Signature, Error> {
426-
let mut r = buf.reader(1);
427-
let mut resp = r.read_string()?.reader(0);
428-
let typ = resp.read_string()?;
429-
let sig = resp.read_string()?;
430-
use crate::signature::Signature;
431-
match typ {
432-
b"ssh-rsa" => Ok(Signature::RSA {
433-
bytes: sig.to_vec(),
434-
hash: SignatureHash::SHA1,
435-
}),
436-
b"rsa-sha2-256" => Ok(Signature::RSA {
437-
bytes: sig.to_vec(),
438-
hash: SignatureHash::SHA2_256,
439-
}),
440-
b"rsa-sha2-512" => Ok(Signature::RSA {
441-
bytes: sig.to_vec(),
442-
hash: SignatureHash::SHA2_512,
443-
}),
444-
b"ssh-ed25519" => {
445-
let mut sig_bytes = [0; 64];
446-
sig_bytes.clone_from_slice(sig);
447-
Ok(Signature::Ed25519(crate::signature::SignatureBytes(
448-
sig_bytes,
449-
)))
450-
}
451-
_ => Err(Error::UnknownSignatureType {
452-
sig_type: std::str::from_utf8(typ).unwrap_or("").to_string(),
453-
}),
454-
}
455-
};
456-
let sig = as_sig(&self.buf);
457-
(self, sig)
458-
} else {
459-
(self, Err(Error::AgentProtocolError))
460-
}
384+
#[allow(clippy::indexing_slicing)] // length is checked
385+
if !self.buf.is_empty() && self.buf[0] == msg::SIGN_RESPONSE {
386+
let mut r = self.buf.reader(1);
387+
let mut resp = r.read_string()?.reader(0);
388+
let typ = String::from_utf8(resp.read_string()?.into())?;
389+
let sig = resp.read_string()?;
390+
let algo = Algorithm::new(&typ)?;
391+
let sig = Signature::new(algo, sig.to_vec())?;
392+
Ok(sig)
393+
} else {
394+
Err(Error::AgentProtocolError)
461395
}
462396
}
463397

464398
/// Ask the agent to remove a key from its memory.
465-
pub async fn remove_identity(&mut self, public: &key::PublicKey) -> Result<(), Error> {
399+
pub async fn remove_identity(&mut self, public: &ssh_key::PublicKey) -> Result<(), Error> {
466400
self.buf.clear();
467401
self.buf.resize(4);
468402
self.buf.push(msg::REMOVE_IDENTITY);
@@ -527,29 +461,7 @@ impl<S: AgentStream + Unpin> AgentClient<S> {
527461
}
528462
}
529463

530-
fn key_blob(public: &key::PublicKey, buf: &mut CryptoVec) -> Result<(), Error> {
531-
match *public {
532-
PublicKey::RSA { ref key, .. } => {
533-
buf.extend(&[0, 0, 0, 0]);
534-
let len0 = buf.len();
535-
buf.extend_ssh_string(b"ssh-rsa");
536-
buf.extend_ssh(&protocol::RsaPublicKey::from(key));
537-
let len1 = buf.len();
538-
#[allow(clippy::indexing_slicing)] // length is known
539-
BigEndian::write_u32(&mut buf[5..], (len1 - len0) as u32);
540-
}
541-
PublicKey::Ed25519(ref p) => {
542-
buf.extend(&[0, 0, 0, 0]);
543-
let len0 = buf.len();
544-
buf.extend_ssh_string(b"ssh-ed25519");
545-
buf.extend_ssh_string(p.as_bytes());
546-
let len1 = buf.len();
547-
#[allow(clippy::indexing_slicing)] // length is known
548-
BigEndian::write_u32(&mut buf[5..], (len1 - len0) as u32);
549-
}
550-
PublicKey::EC { .. } => {
551-
buf.extend_ssh_string(&public.public_key_bytes());
552-
}
553-
}
464+
fn key_blob(public: &ssh_key::PublicKey, buf: &mut CryptoVec) -> Result<(), Error> {
465+
buf.extend_ssh_string(public.key_data().encoded()?.as_slice());
554466
Ok(())
555467
}

0 commit comments

Comments
 (0)