Skip to content

Commit

Permalink
ecdsa-sha2-nistp256 support in the client WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Eugeny committed Nov 5, 2023
1 parent 1a326ad commit 05e7f79
Show file tree
Hide file tree
Showing 13 changed files with 147 additions and 52 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@ This is a fork of [Thrussh](https://nest.pijul.com/pijul/thrussh) by Pierre-Éti
* `hmac-sha1-etm@openssh.com`
* `hmac-sha2-256-etm@openssh.com`
* `hmac-sha2-512-etm@openssh.com`
* Host keys:
* Key types:
* `ssh-ed25519`
* `rsa-sha2-256`
* `rsa-sha2-512`
* `ssh-rsa`
* `ecdsa-sha2-nistp256`
* Dependency updates
* OpenSSH keepalive request handling ✨
* OpenSSH agent forwarding channels ✨
Expand Down
1 change: 1 addition & 0 deletions russh-keys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ block-padding = { version = "0.3", features = ["std"] }
byteorder = "1.4"
data-encoding = "2.3"
dirs = "5.0"
ecdsa = { version = "0.16", features = ["serde"] }
ed25519-dalek = { version= "2.0", features = ["rand_core"] }
futures = "0.3"
hmac = "0.12"
Expand Down
3 changes: 3 additions & 0 deletions russh-keys/src/agent/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ impl<S: AsyncRead + AsyncWrite + Unpin> AgentClient<S> {
self.buf.extend(pair.verifying_key().as_bytes());
self.buf.extend_ssh_string(b"");
}
key::KeyPair::EcdsaSha2NistP256(ref key) => {
todo!("TODO");
}
#[cfg(feature = "openssl")]
#[allow(clippy::unwrap_used)] // key is known to be private
key::KeyPair::RSA { ref key, .. } => {
Expand Down
3 changes: 3 additions & 0 deletions russh-keys/src/deps.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub use ed25519_dalek;
pub use ecdsa;
pub use p256;
21 changes: 20 additions & 1 deletion russh-keys/src/format/openssh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use ctr::Ctr64BE;
use openssl::bn::BigNum;

use crate::encoding::Reader;
use crate::{key, Error, KEYTYPE_ED25519, KEYTYPE_RSA};
use crate::{key, Error, KEYTYPE_ED25519, KEYTYPE_P256, KEYTYPE_RSA};

/// Decode a secret key given in the OpenSSH format, deciphering it if
/// needed using the supplied password.
Expand Down Expand Up @@ -79,6 +79,25 @@ pub fn decode_openssh(secret: &[u8], password: Option<&str>) -> Result<key::KeyP
hash: key::SignatureHash::SHA2_512,
});
}
} else if key_type == KEYTYPE_P256 {
let curve = position.read_string()?;
if curve != b"nistp256" {
return Err(Error::P256KeyError(p256::elliptic_curve::Error));
}

let data = position.read_string()?;
let (check, _public_key_bytes) = data.split_at(1);
if check.get(0) != Some(&4) {
return Err(Error::P256KeyError(p256::elliptic_curve::Error));
}

let data = position.read_string()?;
let (_check, private_key_bytes) = data.split_at(1);

let field = p256::FieldBytes::from_slice(private_key_bytes);
return p256::SecretKey::from_bytes(field)
.map_err(|e| Error::P256KeyError(e))
.map(|k| key::KeyPair::EcdsaSha2NistP256(k));
} else {
return Err(Error::UnsupportedKeyType {
key_type_string: String::from_utf8(key_type.to_vec())
Expand Down
2 changes: 2 additions & 0 deletions russh-keys/src/format/pkcs8.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ fn test_read_write_pkcs8() {
let key = decode_pkcs8(&ciphertext, Some(password)).unwrap();
match key {
key::KeyPair::Ed25519 { .. } => println!("Ed25519"),
key::KeyPair::EcdsaSha2NistP256(_) => println!("P256"),
#[cfg(feature = "openssl")]
key::KeyPair::RSA { .. } => println!("RSA"),
}
Expand Down Expand Up @@ -316,6 +317,7 @@ pub fn encode_pkcs8(key: &key::KeyPair) -> Vec<u8> {
yasna::construct_der(|writer| {
writer.write_sequence(|writer| match *key {
key::KeyPair::Ed25519(ref pair) => write_key_v1(writer, pair),
key::KeyPair::EcdsaSha2NistP256(_) => todo!("TODO"),
#[cfg(feature = "openssl")]
key::KeyPair::RSA { ref key, .. } => write_key_v0(writer, key),
})
Expand Down
56 changes: 53 additions & 3 deletions russh-keys/src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
//
use std::convert::TryFrom;

use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use ecdsa::elliptic_curve::ff::PrimeField;
use ed25519_dalek::{Signer, Verifier};
#[cfg(feature = "openssl")]
use openssl::pkey::{Private, Public};
Expand All @@ -22,7 +24,7 @@ use rand_core::OsRng;
use russh_cryptovec::CryptoVec;
use serde::{Deserialize, Serialize};

use crate::encoding::{Encoding, Reader};
use crate::encoding::{mpint_len, Encoding, Reader};
pub use crate::signature::*;
use crate::Error;

Expand Down Expand Up @@ -215,7 +217,7 @@ impl PublicKey {
let mut p = pubkey.reader(0);
let key_algo = p.read_string()?;
let curve = p.read_string()?;
if key_algo != b"ecdsa-sha2-nistp256" || curve != b"nistp256" {
if key_algo != ECDSA_SHA2_NISTP256.0.as_bytes() || curve != b"nistp256" {
return Err(Error::CouldNotReadKey);
}
let sec1_bytes = p.read_string()?;
Expand Down Expand Up @@ -325,6 +327,7 @@ impl Verify for PublicKey {
#[allow(clippy::large_enum_variant)]
pub enum KeyPair {
Ed25519(ed25519_dalek::SigningKey),
EcdsaSha2NistP256(p256::SecretKey),
#[cfg(feature = "openssl")]
RSA {
key: openssl::rsa::Rsa<Private>,
Expand All @@ -339,6 +342,8 @@ impl Clone for KeyPair {
Self::Ed25519(kp) => {
Self::Ed25519(ed25519_dalek::SigningKey::from_bytes(&kp.to_bytes()))
}
#[allow(clippy::expect_used)]
Self::EcdsaSha2NistP256(k) => Self::EcdsaSha2NistP256(k.clone()),
#[cfg(feature = "openssl")]
Self::RSA { key, hash } => Self::RSA {
key: key.clone(),
Expand All @@ -356,6 +361,7 @@ impl std::fmt::Debug for KeyPair {
"Ed25519 {{ public: {:?}, secret: (hidden) }}",
key.verifying_key().as_bytes()
),
KeyPair::EcdsaSha2NistP256 { .. } => write!(f, "P256 {{ (hidden) }}"),
#[cfg(feature = "openssl")]
KeyPair::RSA { .. } => write!(f, "RSA {{ (hidden) }}"),
}
Expand All @@ -373,6 +379,7 @@ impl KeyPair {
pub fn clone_public_key(&self) -> Result<PublicKey, Error> {
Ok(match self {
KeyPair::Ed25519(ref key) => PublicKey::Ed25519(key.verifying_key()),
KeyPair::EcdsaSha2NistP256(ref key) => PublicKey::P256(key.public_key()),
#[cfg(feature = "openssl")]
KeyPair::RSA { ref key, ref hash } => {
use openssl::pkey::PKey;
Expand All @@ -390,6 +397,7 @@ impl KeyPair {
pub fn name(&self) -> &'static str {
match *self {
KeyPair::Ed25519(_) => ED25519.0,
KeyPair::EcdsaSha2NistP256(_) => ECDSA_SHA2_NISTP256.0,
#[cfg(feature = "openssl")]
KeyPair::RSA { ref hash, .. } => hash.name().0,
}
Expand All @@ -414,10 +422,12 @@ impl KeyPair {
/// Sign a slice using this algorithm.
pub fn sign_detached(&self, to_sign: &[u8]) -> Result<Signature, Error> {
match self {
#[allow(clippy::unwrap_used)]
KeyPair::Ed25519(ref secret) => Ok(Signature::Ed25519(SignatureBytes(
secret.sign(to_sign).to_bytes(),
))),
KeyPair::EcdsaSha2NistP256(ref secret) => Ok(Signature::P256({
p256_signature(secret, to_sign)
})),
#[cfg(feature = "openssl")]
KeyPair::RSA { ref key, ref hash } => Ok(Signature::RSA {
bytes: rsa_signature(hash, key, to_sign)?,
Expand All @@ -443,6 +453,10 @@ impl KeyPair {
buffer.extend_ssh_string(ED25519.0.as_bytes());
buffer.extend_ssh_string(signature.to_bytes().as_slice());
}
KeyPair::EcdsaSha2NistP256(ref secret) => {
let signature = p256_signature(secret, to_sign.as_ref());
encode_ecdsa_signature(buffer, &ECDSA_SHA2_NISTP256, &signature);
}
#[cfg(feature = "openssl")]
KeyPair::RSA { ref key, ref hash } => {
// https://tools.ietf.org/html/draft-rsa-dsa-sha2-256-02#section-2.2
Expand All @@ -468,6 +482,10 @@ impl KeyPair {
buffer.extend_ssh_string(ED25519.0.as_bytes());
buffer.extend_ssh_string(signature.to_bytes().as_slice());
}
KeyPair::EcdsaSha2NistP256(ref secret) => {
let signature = p256_signature(secret, buffer.as_ref());
encode_ecdsa_signature(buffer, &ECDSA_SHA2_NISTP256, &signature);
}
#[cfg(feature = "openssl")]
KeyPair::RSA { ref key, ref hash } => {
// https://tools.ietf.org/html/draft-rsa-dsa-sha2-256-02#section-2.2
Expand All @@ -486,6 +504,7 @@ impl KeyPair {
pub fn with_signature_hash(&self, hash: SignatureHash) -> Option<Self> {
match self {
KeyPair::Ed25519(_) => None,
KeyPair::EcdsaSha2NistP256(_) => None,
#[cfg(feature = "openssl")]
KeyPair::RSA { key, .. } => Some(KeyPair::RSA {
key: key.clone(),
Expand Down Expand Up @@ -519,6 +538,37 @@ fn rsa_signature(
Ok(signer.sign_to_vec()?)
}

fn p256_signature(
secret: &p256::SecretKey,
to_sign: &[u8],
) -> p256::ecdsa::Signature {
let signer = p256::ecdsa::SigningKey::from(secret);
signer.sign(to_sign)
}

pub(crate) fn encode_ecdsa_signature<E: Encoding + WriteBytesExt>(
buffer: &mut E,
name: &Name,
signature: &p256::ecdsa::Signature,
) {
let r = signature.r().to_repr();
let s = signature.s().to_repr();

let mut blob = vec![];
blob.extend_ssh_mpint(&r);
blob.extend_ssh_mpint(&s);

#[allow(clippy::unwrap_used)] // Vec<>.write_all can't fail
buffer
// .write_u32::<BigEndian>((name.0.len() + mpint_len(&r) + mpint_len(&s) + 4) as u32)
.write_u32::<BigEndian>((name.0.len() + blob.len() + 8) as u32)
.unwrap();
buffer.extend_ssh_string(name.0.as_bytes());
buffer.extend_ssh_string(&blob[..]);
// buffer.extend_ssh_mpint(&r);
// buffer.extend_ssh_mpint(&s);
}

/// Parse a public key from a byte slice.
pub fn parse_public_key(
p: &[u8],
Expand Down
8 changes: 8 additions & 0 deletions russh-keys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ pub mod key;
pub mod signature;

mod format;
pub mod deps;
pub use format::*;

/// A module to write SSH agent.
Expand Down Expand Up @@ -163,6 +164,7 @@ impl From<yasna::ASN1Error> for Error {
}

const KEYTYPE_ED25519: &[u8] = b"ssh-ed25519";
const KEYTYPE_P256: &[u8] = b"ecdsa-sha2-nistp256";
const KEYTYPE_RSA: &[u8] = b"ssh-rsa";

/// Load a public key from a file. Ed25519, EC-DSA and RSA keys are supported.
Expand Down Expand Up @@ -261,6 +263,12 @@ impl PublicKeyBase64 for key::KeyPair {
s.write_u32::<BigEndian>(public.len() as u32).unwrap();
s.extend_from_slice(public.as_slice());
}
key::KeyPair::EcdsaSha2NistP256(ref key) => {
let public = key.public_key().to_sec1_bytes();
#[allow(clippy::unwrap_used)] // Vec<>.write can't fail
s.write_u32::<BigEndian>(public.len() as u32).unwrap();
s.extend_from_slice(&public);
},
#[cfg(feature = "openssl")]
key::KeyPair::RSA { ref key, .. } => {
use encoding::Encoding;
Expand Down
20 changes: 8 additions & 12 deletions russh-keys/src/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use serde::de::{SeqAccess, Visitor};
use serde::ser::SerializeTuple;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use crate::key::SignatureHash;
use crate::key::{SignatureHash, ECDSA_SHA2_NISTP256, encode_ecdsa_signature};
use crate::Error;

pub struct SignatureBytes(pub [u8; 64]);
Expand All @@ -17,7 +17,7 @@ pub enum Signature {
/// An Ed25519 signature
Ed25519(SignatureBytes),
/// An EC-DSA NIST P-256 signature
P256(Vec<u8>),
P256(p256::ecdsa::Signature),
/// An RSA signature
RSA { hash: SignatureHash, bytes: Vec<u8> },
}
Expand All @@ -36,14 +36,8 @@ impl Signature {
bytes_.extend_ssh_string(t);
bytes_.extend_ssh_string(&bytes.0[..]);
}
Signature::P256(ref bytes) => {
let t = b"ecdsa-sha2-nistp256";
#[allow(clippy::unwrap_used)] // Vec<>.write_all can't fail
bytes_
.write_u32::<BigEndian>((t.len() + bytes.len() + 8) as u32)
.unwrap();
bytes_.extend_ssh_string(t);
bytes_.extend_ssh_string(bytes);
Signature::P256(ref signature) => {
encode_ecdsa_signature(&mut bytes_, &ECDSA_SHA2_NISTP256, &signature);
}
Signature::RSA {
ref hash,
Expand Down Expand Up @@ -91,7 +85,9 @@ impl Signature {
hash: SignatureHash::SHA1,
bytes: bytes.to_vec(),
}),
b"ecdsa-sha2-nistp256" => Ok(Signature::P256(bytes.to_vec())),
b"ecdsa-sha2-nistp256" => Ok(Signature::P256(
p256::ecdsa::Signature::from_slice(bytes)?,
)),
_ => Err(Error::UnknownSignatureType {
sig_type: std::str::from_utf8(typ).unwrap_or("").to_string(),
}),
Expand All @@ -104,7 +100,7 @@ impl AsRef<[u8]> for Signature {
match *self {
Signature::Ed25519(ref signature) => &signature.0,
Signature::RSA { ref bytes, .. } => &bytes[..],
Signature::P256(ref signature) => signature,
Signature::P256(ref signature) => todo!(),
}
}
}
Expand Down
1 change: 0 additions & 1 deletion russh/examples/client_exec_interactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ use tokio::net::ToSocketAddrs;
#[tokio::main]
async fn main() -> Result<()> {
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.init();

// CLI options are defined later in this file
Expand Down
2 changes: 1 addition & 1 deletion russh/examples/client_exec_simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use tokio::net::ToSocketAddrs;
#[tokio::main]
async fn main() -> Result<()> {
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.init();

// CLI options are defined later in this file
Expand Down Expand Up @@ -81,6 +80,7 @@ impl Session {
addrs: A,
) -> Result<Self> {
let key_pair = load_secret_key(key_path, None)?;
println!("{:?}", key_pair.public_key_base64());
let config = client::Config {
inactivity_timeout: Some(Duration::from_secs(5)),
..<_>::default()
Expand Down
Loading

0 comments on commit 05e7f79

Please sign in to comment.