Skip to content

Commit

Permalink
Support for NIST P-521 public keys
Browse files Browse the repository at this point in the history
  • Loading branch information
akeamc authored and Eugeny committed Jan 23, 2024
1 parent 54595f3 commit 2ce82f2
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 6 deletions.
1 change: 1 addition & 0 deletions russh-keys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ tokio = { version = "1.17.0", features = [
] }
tokio-stream = { version = "0.1", features = ["net"] }
yasna = { version = "0.5.0", features = ["bit-vec", "num-bigint"] }
p521 = "0.13.3"

[features]
vendored-openssl = ["openssl", "openssl/vendored"]
Expand Down
12 changes: 10 additions & 2 deletions russh-keys/src/agent/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,11 +278,19 @@ impl<S: AsyncRead + AsyncWrite + Unpin> AgentClient<S> {
b"ecdsa-sha2-nistp256" => {
let curve = r.read_string()?;
if curve != b"nistp256" {
return Err(Error::P256KeyError(p256::elliptic_curve::Error));
return Err(Error::EcdsaKeyError(p256::elliptic_curve::Error));
}
let key = r.read_string()?;
keys.push(PublicKey::P256(p256::PublicKey::from_sec1_bytes(key)?));
}
b"ecdsa-sha2-nistp512" => {
let curve = r.read_string()?;
if curve != b"nistp521" {
return Err(Error::EcdsaKeyError(p521::elliptic_curve::Error));
}
let key = r.read_string()?;
keys.push(PublicKey::P521(p521::PublicKey::from_sec1_bytes(key)?));
}
t => {
info!("Unsupported key type: {:?}", std::str::from_utf8(t))
}
Expand Down Expand Up @@ -542,7 +550,7 @@ fn key_blob(public: &key::PublicKey, buf: &mut CryptoVec) -> Result<(), Error> {
#[allow(clippy::indexing_slicing)] // length is known
BigEndian::write_u32(&mut buf[5..], (len1 - len0) as u32);
}
PublicKey::P256(_) => {
PublicKey::P256(_) | PublicKey::P521(_) => {
buf.extend_ssh_string(&public.public_key_bytes());
}
}
Expand Down
54 changes: 53 additions & 1 deletion russh-keys/src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
//
use std::convert::TryFrom;

use block_padding::generic_array::GenericArray;
use ed25519_dalek::{Signer, Verifier};
#[cfg(feature = "openssl")]
use openssl::pkey::{Private, Public};
Expand All @@ -38,6 +39,8 @@ impl AsRef<str> for Name {

/// The name of the ecdsa-sha2-nistp256 algorithm for SSH.
pub const ECDSA_SHA2_NISTP256: Name = Name("ecdsa-sha2-nistp256");
/// The name of the ecdsa-sha2-nistp521 algorithm for SSH.
pub const ECDSA_SHA2_NISTP521: Name = Name("ecdsa-sha2-nistp521");
/// The name of the Ed25519 algorithm for SSH.
pub const ED25519: Name = Name("ssh-ed25519");
/// The name of the ssh-sha2-512 algorithm for SSH.
Expand All @@ -53,7 +56,7 @@ impl Name {
/// Base name of the private key file for a key name.
pub fn identity_file(&self) -> &'static str {
match *self {
ECDSA_SHA2_NISTP256 => "id_ecdsa",
ECDSA_SHA2_NISTP256 | ECDSA_SHA2_NISTP521 => "id_ecdsa",
ED25519 => "id_ed25519",
RSA_SHA2_512 => "id_rsa",
RSA_SHA2_256 => "id_rsa",
Expand Down Expand Up @@ -123,6 +126,8 @@ pub enum PublicKey {
},
#[doc(hidden)]
P256(p256::PublicKey),
#[doc(hidden)]
P521(p521::PublicKey),
}

impl PartialEq for PublicKey {
Expand All @@ -132,6 +137,7 @@ impl PartialEq for PublicKey {
(Self::RSA { key: a, .. }, Self::RSA { key: b, .. }) => a == b,
(Self::Ed25519(a), Self::Ed25519(b)) => a == b,
(Self::P256(a), Self::P256(b)) => a == b,
(Self::P521(a), Self::P521(b)) => a == b,
_ => false,
}
}
Expand Down Expand Up @@ -223,6 +229,18 @@ impl PublicKey {
.map_err(|_| Error::CouldNotReadKey)?;
Ok(PublicKey::P256(key))
}
b"ecdsa-sha2-nistp521" => {
let mut p = pubkey.reader(0);
let key_algo = p.read_string()?;
let curve = p.read_string()?;
if key_algo != b"ecdsa-sha2-nistp521" || curve != b"nistp521" {
return Err(Error::CouldNotReadKey);
}
let sec1_bytes = p.read_string()?;
let key = p521::PublicKey::from_sec1_bytes(sec1_bytes)
.map_err(|_| Error::CouldNotReadKey)?;
Ok(PublicKey::P521(key))
}
_ => Err(Error::CouldNotReadKey),
}
}
Expand All @@ -234,6 +252,7 @@ impl PublicKey {
#[cfg(feature = "openssl")]
PublicKey::RSA { ref hash, .. } => hash.name().0,
PublicKey::P256(_) => ECDSA_SHA2_NISTP256.0,
PublicKey::P521(_) => ECDSA_SHA2_NISTP521.0,
}
}

Expand Down Expand Up @@ -282,6 +301,31 @@ impl PublicKey {
.verify(buffer, &signature)
.is_ok()
}
PublicKey::P521(ref public) => {
const FIELD_LEN: usize =
<p521::NistP521 as p256::elliptic_curve::Curve>::FieldBytesSize::USIZE;
let mut reader = sig.reader(0);
let mut read_field = || -> Option<p521::FieldBytes> {
let f = reader.read_mpint().ok()?;
let f = f.strip_prefix(&[0]).unwrap_or(f);
let mut result = [0; FIELD_LEN];
if f.len() > FIELD_LEN {
return None;
}
#[allow(clippy::indexing_slicing)] // length is known
result[FIELD_LEN - f.len()..].copy_from_slice(f);
// Some(result.into())
Some(GenericArray::clone_from_slice(&result)) // ew
};
let Some(r) = read_field() else { return false };
let Some(s) = read_field() else { return false };
let Ok(signature) = p521::ecdsa::Signature::from_scalars(r, s) else {
return false;
};
p521::ecdsa::VerifyingKey::from_sec1_bytes(&public.to_sec1_bytes()).unwrap() // also ew
.verify(buffer, &signature)
.is_ok()
}
}
}

Expand Down Expand Up @@ -560,5 +604,13 @@ pub fn parse_public_key(
let key = p256::PublicKey::from_sec1_bytes(sec1_bytes)?;
return Ok(PublicKey::P256(key));
}
if t == b"ecdsa-sha2-nistp521" {
if pos.read_string()? != b"nistp521" {
return Err(Error::CouldNotReadKey);
}
let sec1_bytes = pos.read_string()?;
let key = p521::PublicKey::from_sec1_bytes(sec1_bytes)?;
return Ok(PublicKey::P521(key));
}
Err(Error::CouldNotReadKey)
}
10 changes: 8 additions & 2 deletions russh-keys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ pub enum Error {
#[error("Invalid Ed25519 key data")]
Ed25519KeyError(#[from] ed25519_dalek::SignatureError),
/// The type of the key is unsupported
#[error("Invalid NIST-P256 key data")]
P256KeyError(#[from] p256::elliptic_curve::Error),
#[error("Invalid ECDSA key data")]
EcdsaKeyError(#[from] p256::elliptic_curve::Error),
/// The key is encrypted (should supply a password?)
#[error("The key is encrypted")]
KeyIsEncrypted,
Expand Down Expand Up @@ -242,6 +242,12 @@ impl PublicKeyBase64 for key::PublicKey {
s.extend_ssh_string(b"nistp256");
s.extend_ssh_string(&publickey.to_sec1_bytes());
}
key::PublicKey::P521(ref publickey) => {
use encoding::Encoding;
s.extend_ssh_string(b"ecdsa-sha2-nistp521");
s.extend_ssh_string(b"nistp521");
s.extend_ssh_string(&publickey.to_sec1_bytes());
}
}
s
}
Expand Down
4 changes: 3 additions & 1 deletion russh/src/negotiation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ impl Preferred {
key: &[
key::ED25519,
key::ECDSA_SHA2_NISTP256,
key::ECDSA_SHA2_NISTP521,
#[cfg(feature = "openssl")]
key::RSA_SHA2_256,
#[cfg(feature = "openssl")]
Expand Down Expand Up @@ -126,13 +127,14 @@ impl Named for () {

#[cfg(feature = "openssl")]
use russh_keys::key::SSH_RSA;
use russh_keys::key::{ECDSA_SHA2_NISTP256, ED25519};
use russh_keys::key::{ECDSA_SHA2_NISTP256, ECDSA_SHA2_NISTP521, ED25519};

impl Named for PublicKey {
fn name(&self) -> &'static str {
match self {
PublicKey::Ed25519(_) => ED25519.0,
PublicKey::P256(_) => ECDSA_SHA2_NISTP256.0,
PublicKey::P521(_) => ECDSA_SHA2_NISTP521.0,
#[cfg(feature = "openssl")]
PublicKey::RSA { .. } => SSH_RSA.0,
}
Expand Down

0 comments on commit 2ce82f2

Please sign in to comment.