Skip to content

Commit 2ce82f2

Browse files
akeamcEugeny
authored andcommitted
Support for NIST P-521 public keys
1 parent 54595f3 commit 2ce82f2

File tree

5 files changed

+75
-6
lines changed

5 files changed

+75
-6
lines changed

russh-keys/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ tokio = { version = "1.17.0", features = [
6565
] }
6666
tokio-stream = { version = "0.1", features = ["net"] }
6767
yasna = { version = "0.5.0", features = ["bit-vec", "num-bigint"] }
68+
p521 = "0.13.3"
6869

6970
[features]
7071
vendored-openssl = ["openssl", "openssl/vendored"]

russh-keys/src/agent/client.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,11 +278,19 @@ impl<S: AsyncRead + AsyncWrite + Unpin> AgentClient<S> {
278278
b"ecdsa-sha2-nistp256" => {
279279
let curve = r.read_string()?;
280280
if curve != b"nistp256" {
281-
return Err(Error::P256KeyError(p256::elliptic_curve::Error));
281+
return Err(Error::EcdsaKeyError(p256::elliptic_curve::Error));
282282
}
283283
let key = r.read_string()?;
284284
keys.push(PublicKey::P256(p256::PublicKey::from_sec1_bytes(key)?));
285285
}
286+
b"ecdsa-sha2-nistp512" => {
287+
let curve = r.read_string()?;
288+
if curve != b"nistp521" {
289+
return Err(Error::EcdsaKeyError(p521::elliptic_curve::Error));
290+
}
291+
let key = r.read_string()?;
292+
keys.push(PublicKey::P521(p521::PublicKey::from_sec1_bytes(key)?));
293+
}
286294
t => {
287295
info!("Unsupported key type: {:?}", std::str::from_utf8(t))
288296
}
@@ -542,7 +550,7 @@ fn key_blob(public: &key::PublicKey, buf: &mut CryptoVec) -> Result<(), Error> {
542550
#[allow(clippy::indexing_slicing)] // length is known
543551
BigEndian::write_u32(&mut buf[5..], (len1 - len0) as u32);
544552
}
545-
PublicKey::P256(_) => {
553+
PublicKey::P256(_) | PublicKey::P521(_) => {
546554
buf.extend_ssh_string(&public.public_key_bytes());
547555
}
548556
}

russh-keys/src/key.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
//
1515
use std::convert::TryFrom;
1616

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

3940
/// The name of the ecdsa-sha2-nistp256 algorithm for SSH.
4041
pub const ECDSA_SHA2_NISTP256: Name = Name("ecdsa-sha2-nistp256");
42+
/// The name of the ecdsa-sha2-nistp521 algorithm for SSH.
43+
pub const ECDSA_SHA2_NISTP521: Name = Name("ecdsa-sha2-nistp521");
4144
/// The name of the Ed25519 algorithm for SSH.
4245
pub const ED25519: Name = Name("ssh-ed25519");
4346
/// The name of the ssh-sha2-512 algorithm for SSH.
@@ -53,7 +56,7 @@ impl Name {
5356
/// Base name of the private key file for a key name.
5457
pub fn identity_file(&self) -> &'static str {
5558
match *self {
56-
ECDSA_SHA2_NISTP256 => "id_ecdsa",
59+
ECDSA_SHA2_NISTP256 | ECDSA_SHA2_NISTP521 => "id_ecdsa",
5760
ED25519 => "id_ed25519",
5861
RSA_SHA2_512 => "id_rsa",
5962
RSA_SHA2_256 => "id_rsa",
@@ -123,6 +126,8 @@ pub enum PublicKey {
123126
},
124127
#[doc(hidden)]
125128
P256(p256::PublicKey),
129+
#[doc(hidden)]
130+
P521(p521::PublicKey),
126131
}
127132

128133
impl PartialEq for PublicKey {
@@ -132,6 +137,7 @@ impl PartialEq for PublicKey {
132137
(Self::RSA { key: a, .. }, Self::RSA { key: b, .. }) => a == b,
133138
(Self::Ed25519(a), Self::Ed25519(b)) => a == b,
134139
(Self::P256(a), Self::P256(b)) => a == b,
140+
(Self::P521(a), Self::P521(b)) => a == b,
135141
_ => false,
136142
}
137143
}
@@ -223,6 +229,18 @@ impl PublicKey {
223229
.map_err(|_| Error::CouldNotReadKey)?;
224230
Ok(PublicKey::P256(key))
225231
}
232+
b"ecdsa-sha2-nistp521" => {
233+
let mut p = pubkey.reader(0);
234+
let key_algo = p.read_string()?;
235+
let curve = p.read_string()?;
236+
if key_algo != b"ecdsa-sha2-nistp521" || curve != b"nistp521" {
237+
return Err(Error::CouldNotReadKey);
238+
}
239+
let sec1_bytes = p.read_string()?;
240+
let key = p521::PublicKey::from_sec1_bytes(sec1_bytes)
241+
.map_err(|_| Error::CouldNotReadKey)?;
242+
Ok(PublicKey::P521(key))
243+
}
226244
_ => Err(Error::CouldNotReadKey),
227245
}
228246
}
@@ -234,6 +252,7 @@ impl PublicKey {
234252
#[cfg(feature = "openssl")]
235253
PublicKey::RSA { ref hash, .. } => hash.name().0,
236254
PublicKey::P256(_) => ECDSA_SHA2_NISTP256.0,
255+
PublicKey::P521(_) => ECDSA_SHA2_NISTP521.0,
237256
}
238257
}
239258

@@ -282,6 +301,31 @@ impl PublicKey {
282301
.verify(buffer, &signature)
283302
.is_ok()
284303
}
304+
PublicKey::P521(ref public) => {
305+
const FIELD_LEN: usize =
306+
<p521::NistP521 as p256::elliptic_curve::Curve>::FieldBytesSize::USIZE;
307+
let mut reader = sig.reader(0);
308+
let mut read_field = || -> Option<p521::FieldBytes> {
309+
let f = reader.read_mpint().ok()?;
310+
let f = f.strip_prefix(&[0]).unwrap_or(f);
311+
let mut result = [0; FIELD_LEN];
312+
if f.len() > FIELD_LEN {
313+
return None;
314+
}
315+
#[allow(clippy::indexing_slicing)] // length is known
316+
result[FIELD_LEN - f.len()..].copy_from_slice(f);
317+
// Some(result.into())
318+
Some(GenericArray::clone_from_slice(&result)) // ew
319+
};
320+
let Some(r) = read_field() else { return false };
321+
let Some(s) = read_field() else { return false };
322+
let Ok(signature) = p521::ecdsa::Signature::from_scalars(r, s) else {
323+
return false;
324+
};
325+
p521::ecdsa::VerifyingKey::from_sec1_bytes(&public.to_sec1_bytes()).unwrap() // also ew
326+
.verify(buffer, &signature)
327+
.is_ok()
328+
}
285329
}
286330
}
287331

@@ -560,5 +604,13 @@ pub fn parse_public_key(
560604
let key = p256::PublicKey::from_sec1_bytes(sec1_bytes)?;
561605
return Ok(PublicKey::P256(key));
562606
}
607+
if t == b"ecdsa-sha2-nistp521" {
608+
if pos.read_string()? != b"nistp521" {
609+
return Err(Error::CouldNotReadKey);
610+
}
611+
let sec1_bytes = pos.read_string()?;
612+
let key = p521::PublicKey::from_sec1_bytes(sec1_bytes)?;
613+
return Ok(PublicKey::P521(key));
614+
}
563615
Err(Error::CouldNotReadKey)
564616
}

russh-keys/src/lib.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ pub enum Error {
102102
#[error("Invalid Ed25519 key data")]
103103
Ed25519KeyError(#[from] ed25519_dalek::SignatureError),
104104
/// The type of the key is unsupported
105-
#[error("Invalid NIST-P256 key data")]
106-
P256KeyError(#[from] p256::elliptic_curve::Error),
105+
#[error("Invalid ECDSA key data")]
106+
EcdsaKeyError(#[from] p256::elliptic_curve::Error),
107107
/// The key is encrypted (should supply a password?)
108108
#[error("The key is encrypted")]
109109
KeyIsEncrypted,
@@ -242,6 +242,12 @@ impl PublicKeyBase64 for key::PublicKey {
242242
s.extend_ssh_string(b"nistp256");
243243
s.extend_ssh_string(&publickey.to_sec1_bytes());
244244
}
245+
key::PublicKey::P521(ref publickey) => {
246+
use encoding::Encoding;
247+
s.extend_ssh_string(b"ecdsa-sha2-nistp521");
248+
s.extend_ssh_string(b"nistp521");
249+
s.extend_ssh_string(&publickey.to_sec1_bytes());
250+
}
245251
}
246252
s
247253
}

russh/src/negotiation.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ impl Preferred {
8787
key: &[
8888
key::ED25519,
8989
key::ECDSA_SHA2_NISTP256,
90+
key::ECDSA_SHA2_NISTP521,
9091
#[cfg(feature = "openssl")]
9192
key::RSA_SHA2_256,
9293
#[cfg(feature = "openssl")]
@@ -126,13 +127,14 @@ impl Named for () {
126127

127128
#[cfg(feature = "openssl")]
128129
use russh_keys::key::SSH_RSA;
129-
use russh_keys::key::{ECDSA_SHA2_NISTP256, ED25519};
130+
use russh_keys::key::{ECDSA_SHA2_NISTP256, ECDSA_SHA2_NISTP521, ED25519};
130131

131132
impl Named for PublicKey {
132133
fn name(&self) -> &'static str {
133134
match self {
134135
PublicKey::Ed25519(_) => ED25519.0,
135136
PublicKey::P256(_) => ECDSA_SHA2_NISTP256.0,
137+
PublicKey::P521(_) => ECDSA_SHA2_NISTP521.0,
136138
#[cfg(feature = "openssl")]
137139
PublicKey::RSA { .. } => SSH_RSA.0,
138140
}

0 commit comments

Comments
 (0)