chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

View File

@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"a69dc0b24682c02206f1fd9cb81572e825832d25570a1e476ab2e2e3a1bce590","Cargo.toml":"cc69fcabb16b86e53ecd4c961b26b39a3ed851575ef6213f5d015e98911dd5e3","Cargo.toml.orig":"caca0ad2ccfdef75e32d75b9b29d3e4828be0c7ce05259f6caa6d51ffa30c8d6","src/agent/client.rs":"32e6f4319976501044c251d5d3f3e0133b3fa0e998ef4e9c75b3a24ee4b90802","src/agent/mod.rs":"3d85a648533e8246f702125c28487669b8d1d0d988e65825b240d8226847e217","src/agent/msg.rs":"3461e3f12792ddbddc2aaddaca25f4e074e91a3e58488fe6b82dc087590134bc","src/agent/server.rs":"110830b17a8600be16468f7898c1154a262388da4789fba46ccbebb9c77c1514","src/backend_openssl.rs":"fe49062f1378c1bd731d6e0f355bacfd0393b48f4a5a9621542f030ce6503f5b","src/backend_rust.rs":"47a2db63538b4e586d0ba0b6187d6de6f13e84db5fbffdb51ffa9d6ddccfe761","src/ec.rs":"74b76008df2e9d5269cb3eb525a43ab9444d95d9a3e9550dfc899464054a8c19","src/encoding.rs":"ca25e2676a337703828ae9b2af439193e40e97ea160557e6be684a6efcdd4961","src/format/mod.rs":"423a1abe75c863037492317c5f38e03c8d4ed489b9bc5fed54a8ba746b7a4de4","src/format/openssh.rs":"2e607b1a3aa19d305d4a91035a15b36fd6c981a759f8a9a2905734189991a0de","src/format/pkcs5.rs":"2707ea60c49de28c55eeaf0b5bbdee6250c42a8b8ea18e76bb193df603935d0f","src/format/pkcs8.rs":"7f4f39d3dd68bf2fc17cd05bfd6846ab21dd5bdacfb231ec6598e78385137332","src/format/pkcs8_legacy.rs":"9d8489e2a4e429e457e87788af92b8e16580934350caf9aaf794b967a244586b","src/key.rs":"4bb5073e5ae68785a56d53c83c638ee48dfcdf6c25d6be5488fb1056719308d4","src/known_hosts.rs":"5256b21864893dcd910ff52b3342826dbd7b5376bacf4a1c8a993faef790f9b5","src/lib.rs":"33be94d321358287e865baed84f9666d00c0d86d52e72b40a8ec90b8b87fec27","src/protocol.rs":"7dee1e5525990dc7d63fcd88e1b4ecfe2a0e21e2138098bf3e495af0de1d7089","src/signature.rs":"9efaf13ab20a429109bd686e2a7324597a912c433db3d601e68834a980d674e2"},"package":"6e3db166c8678c824627c2c46f619ed5ce4ae33f38a35403c62f6ab8f3985867"}

View File

@@ -0,0 +1,7 @@
{
"git": {
"sha1": "a3ae9836e27111b5e1ad15ac5c84491907d21e86",
"dirty": true
},
"path_in_vcs": "russh-keys"
}

246
vendor/russh-keys/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,246 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
rust-version = "1.65"
name = "russh-keys"
version = "0.46.0"
authors = ["Pierre-Étienne Meunier <pe@pijul.org>"]
build = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Deal with SSH keys: load them, decrypt them, call an SSH agent."
homepage = "https://github.com/warp-tech/russh"
documentation = "https://docs.rs/russh-keys"
readme = false
keywords = ["ssh"]
license = "Apache-2.0"
repository = "https://github.com/warp-tech/russh"
resolver = "2"
[package.metadata.docs.rs]
features = ["openssl"]
[lib]
name = "russh_keys"
path = "src/lib.rs"
[dependencies.aes]
version = "0.8"
[dependencies.async-trait]
version = "0.1"
[dependencies.bcrypt-pbkdf]
version = "0.10"
[dependencies.block-padding]
version = "0.3"
features = ["std"]
[dependencies.byteorder]
version = "1.4"
[dependencies.cbc]
version = "0.1"
[dependencies.ctr]
version = "0.9"
[dependencies.data-encoding]
version = "2.3"
[dependencies.der]
version = "0.7"
[dependencies.digest]
version = "0.10"
[dependencies.ecdsa]
version = "0.16"
[dependencies.ed25519-dalek]
version = "2.0"
features = [
"rand_core",
"pkcs8",
]
[dependencies.elliptic-curve]
version = "0.13"
[dependencies.futures]
version = "0.3"
[dependencies.getrandom]
version = "0.2.15"
features = ["js"]
[dependencies.hmac]
version = "0.12"
[dependencies.inout]
version = "0.1"
features = ["std"]
[dependencies.log]
version = "0.4"
[dependencies.md5]
version = "0.7"
[dependencies.num-integer]
version = "0.1"
[dependencies.openssl]
version = "0.10"
optional = true
[dependencies.p256]
version = "0.13"
[dependencies.p384]
version = "0.13"
[dependencies.p521]
version = "0.13"
[dependencies.pbkdf2]
version = "0.12"
[dependencies.pkcs1]
version = "0.7"
[dependencies.pkcs5]
version = "0.7"
[dependencies.pkcs8]
version = "0.10"
features = [
"pkcs5",
"encryption",
]
[dependencies.rand]
version = "0.8"
[dependencies.rand_core]
version = "0.6.4"
features = ["std"]
[dependencies.rsa]
version = "0.9"
[dependencies.russh-cryptovec]
version = "0.7.0"
[dependencies.russh-util]
version = "0.46.0"
[dependencies.sec1]
version = "0.7"
features = ["pkcs8"]
[dependencies.serde]
version = "1.0"
features = ["derive"]
[dependencies.sha1]
version = "0.10"
features = ["oid"]
[dependencies.sha2]
version = "0.10"
features = ["oid"]
[dependencies.spki]
version = "0.7"
[dependencies.ssh-encoding]
version = "0.2"
[dependencies.ssh-key]
version = "0.6"
features = [
"ed25519",
"rsa",
"encryption",
]
[dependencies.thiserror]
version = "1.0"
[dependencies.tokio]
version = "1.17.0"
features = [
"io-util",
"time",
]
[dependencies.typenum]
version = "1.17"
[dependencies.yasna]
version = "0.5.0"
features = [
"bit-vec",
"num-bigint",
]
optional = true
[dependencies.zeroize]
version = "1.7"
[dev-dependencies.env_logger]
version = "0.11"
[dev-dependencies.tempdir]
version = "0.3"
[dev-dependencies.tokio]
version = "1.17.0"
features = [
"test-util",
"macros",
"process",
]
[features]
legacy-ed25519-pkcs8-parser = ["yasna"]
vendored-openssl = [
"openssl",
"openssl/vendored",
]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.home]
version = "0.5"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
version = "1.17.0"
features = [
"io-util",
"rt-multi-thread",
"time",
"net",
]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio-stream]
version = "0.1"
features = [
"net",
"sync",
]
[target."cfg(windows)".dependencies.pageant]
version = "0.0.1-beta.3"

555
vendor/russh-keys/src/agent/client.rs vendored Normal file
View File

@@ -0,0 +1,555 @@
use std::convert::TryFrom;
use byteorder::{BigEndian, ByteOrder};
use log::debug;
use russh_cryptovec::CryptoVec;
use tokio;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use super::{msg, Constraint};
use crate::encoding::{Encoding, Reader};
use crate::key::{PublicKey, SignatureHash};
use crate::{key, protocol, Error, PublicKeyBase64};
pub trait AgentStream: AsyncRead + AsyncWrite {}
impl<S: AsyncRead + AsyncWrite> AgentStream for S {}
/// SSH agent client.
pub struct AgentClient<S: AgentStream> {
stream: S,
buf: CryptoVec,
}
impl<S: AgentStream + Send + Unpin + 'static> AgentClient<S> {
/// Wraps the internal stream in a Box<dyn _>, allowing different client
/// implementations to have the same type
pub fn dynamic(self) -> AgentClient<Box<dyn AgentStream + Send + Unpin + 'static>> {
AgentClient {
stream: Box::new(self.stream),
buf: self.buf,
}
}
pub fn into_inner(self) -> Box<dyn AgentStream + Send + Unpin + 'static> {
Box::new(self.stream)
}
}
// https://tools.ietf.org/html/draft-miller-ssh-agent-00#section-4.1
impl<S: AgentStream + Unpin> AgentClient<S> {
/// Build a future that connects to an SSH agent via the provided
/// stream (on Unix, usually a Unix-domain socket).
pub fn connect(stream: S) -> Self {
AgentClient {
stream,
buf: CryptoVec::new(),
}
}
}
#[cfg(unix)]
impl AgentClient<tokio::net::UnixStream> {
/// Connect to an SSH agent via the provided
/// stream (on Unix, usually a Unix-domain socket).
pub async fn connect_uds<P: AsRef<std::path::Path>>(path: P) -> Result<Self, Error> {
let stream = tokio::net::UnixStream::connect(path).await?;
Ok(AgentClient {
stream,
buf: CryptoVec::new(),
})
}
/// Connect to an SSH agent specified by the SSH_AUTH_SOCK
/// environment variable.
pub async fn connect_env() -> Result<Self, Error> {
let var = if let Ok(var) = std::env::var("SSH_AUTH_SOCK") {
var
} else {
return Err(Error::EnvVar("SSH_AUTH_SOCK"));
};
match Self::connect_uds(var).await {
Err(Error::IO(io_err)) if io_err.kind() == std::io::ErrorKind::NotFound => {
Err(Error::BadAuthSock)
}
owise => owise,
}
}
}
#[cfg(windows)]
const ERROR_PIPE_BUSY: u32 = 231u32;
#[cfg(windows)]
impl AgentClient<pageant::PageantStream> {
/// Connect to a running Pageant instance
pub async fn connect_pageant() -> Self {
Self::connect(pageant::PageantStream::new())
}
}
#[cfg(windows)]
impl AgentClient<tokio::net::windows::named_pipe::NamedPipeClient> {
/// Connect to an SSH agent via a Windows named pipe
pub async fn connect_named_pipe<P: AsRef<std::ffi::OsStr>>(path: P) -> Result<Self, Error> {
let stream = loop {
match tokio::net::windows::named_pipe::ClientOptions::new().open(path.as_ref()) {
Ok(client) => break client,
Err(e) if e.raw_os_error() == Some(ERROR_PIPE_BUSY as i32) => (),
Err(e) => return Err(e.into()),
}
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
};
Ok(AgentClient {
stream,
buf: CryptoVec::new(),
})
}
}
impl<S: AgentStream + Unpin> AgentClient<S> {
async fn read_response(&mut self) -> Result<(), Error> {
// Writing the message
self.stream.write_all(&self.buf).await?;
self.stream.flush().await?;
// Reading the length
self.buf.clear();
self.buf.resize(4);
self.stream.read_exact(&mut self.buf).await?;
// Reading the rest of the buffer
let len = BigEndian::read_u32(&self.buf) as usize;
self.buf.clear();
self.buf.resize(len);
self.stream.read_exact(&mut self.buf).await?;
Ok(())
}
async fn read_success(&mut self) -> Result<(), Error> {
self.read_response().await?;
if self.buf.first() == Some(&msg::SUCCESS) {
Ok(())
} else {
Err(Error::AgentFailure)
}
}
/// Send a key to the agent, with a (possibly empty) slice of
/// constraints to apply when using the key to sign.
pub async fn add_identity(
&mut self,
key: &key::KeyPair,
constraints: &[Constraint],
) -> Result<(), Error> {
// See IETF draft-miller-ssh-agent-13, section 3.2 for format.
// https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent
self.buf.clear();
self.buf.resize(4);
if constraints.is_empty() {
self.buf.push(msg::ADD_IDENTITY)
} else {
self.buf.push(msg::ADD_ID_CONSTRAINED)
}
match *key {
key::KeyPair::Ed25519(ref pair) => {
self.buf.extend_ssh_string(b"ssh-ed25519");
self.buf.extend_ssh_string(pair.verifying_key().as_bytes());
self.buf.push_u32_be(64);
self.buf.extend(pair.to_bytes().as_slice());
self.buf.extend(pair.verifying_key().as_bytes());
self.buf.extend_ssh_string(b"");
}
#[allow(clippy::unwrap_used)] // key is known to be private
key::KeyPair::RSA { ref key, .. } => {
self.buf.extend_ssh_string(b"ssh-rsa");
self.buf
.extend_ssh(&protocol::RsaPrivateKey::try_from(key)?);
}
key::KeyPair::EC { ref key } => {
self.buf.extend_ssh_string(key.algorithm().as_bytes());
self.buf.extend_ssh_string(key.ident().as_bytes());
self.buf
.extend_ssh_string(&key.to_public_key().to_sec1_bytes());
self.buf.extend_ssh_mpint(&key.to_secret_bytes());
self.buf.extend_ssh_string(b""); // comment
}
}
if !constraints.is_empty() {
for cons in constraints {
match *cons {
Constraint::KeyLifetime { seconds } => {
self.buf.push(msg::CONSTRAIN_LIFETIME);
self.buf.push_u32_be(seconds);
}
Constraint::Confirm => self.buf.push(msg::CONSTRAIN_CONFIRM),
Constraint::Extensions {
ref name,
ref details,
} => {
self.buf.push(msg::CONSTRAIN_EXTENSION);
self.buf.extend_ssh_string(name);
self.buf.extend_ssh_string(details);
}
}
}
}
let len = self.buf.len() - 4;
BigEndian::write_u32(&mut self.buf[..], len as u32);
self.read_success().await?;
Ok(())
}
/// Add a smart card to the agent, with a (possibly empty) set of
/// constraints to apply when signing.
pub async fn add_smartcard_key(
&mut self,
id: &str,
pin: &[u8],
constraints: &[Constraint],
) -> Result<(), Error> {
self.buf.clear();
self.buf.resize(4);
if constraints.is_empty() {
self.buf.push(msg::ADD_SMARTCARD_KEY)
} else {
self.buf.push(msg::ADD_SMARTCARD_KEY_CONSTRAINED)
}
self.buf.extend_ssh_string(id.as_bytes());
self.buf.extend_ssh_string(pin);
if !constraints.is_empty() {
self.buf.push_u32_be(constraints.len() as u32);
for cons in constraints {
match *cons {
Constraint::KeyLifetime { seconds } => {
self.buf.push(msg::CONSTRAIN_LIFETIME);
self.buf.push_u32_be(seconds)
}
Constraint::Confirm => self.buf.push(msg::CONSTRAIN_CONFIRM),
Constraint::Extensions {
ref name,
ref details,
} => {
self.buf.push(msg::CONSTRAIN_EXTENSION);
self.buf.extend_ssh_string(name);
self.buf.extend_ssh_string(details);
}
}
}
}
let len = self.buf.len() - 4;
BigEndian::write_u32(&mut self.buf[..], len as u32);
self.read_response().await?;
Ok(())
}
/// Lock the agent, making it refuse to sign until unlocked.
pub async fn lock(&mut self, passphrase: &[u8]) -> Result<(), Error> {
self.buf.clear();
self.buf.resize(4);
self.buf.push(msg::LOCK);
self.buf.extend_ssh_string(passphrase);
let len = self.buf.len() - 4;
BigEndian::write_u32(&mut self.buf[..], len as u32);
self.read_response().await?;
Ok(())
}
/// Unlock the agent, allowing it to sign again.
pub async fn unlock(&mut self, passphrase: &[u8]) -> Result<(), Error> {
self.buf.clear();
self.buf.resize(4);
self.buf.push(msg::UNLOCK);
self.buf.extend_ssh_string(passphrase);
let len = self.buf.len() - 4;
#[allow(clippy::indexing_slicing)] // static length
BigEndian::write_u32(&mut self.buf[..], len as u32);
self.read_response().await?;
Ok(())
}
/// Ask the agent for a list of the currently registered secret
/// keys.
pub async fn request_identities(&mut self) -> Result<Vec<PublicKey>, Error> {
self.buf.clear();
self.buf.resize(4);
self.buf.push(msg::REQUEST_IDENTITIES);
let len = self.buf.len() - 4;
BigEndian::write_u32(&mut self.buf[..], len as u32);
self.read_response().await?;
debug!("identities: {:?}", &self.buf[..]);
let mut keys = Vec::new();
#[allow(clippy::indexing_slicing)] // static length
if self.buf[0] == msg::IDENTITIES_ANSWER {
let mut r = self.buf.reader(1);
let n = r.read_u32()?;
for _ in 0..n {
let key_blob = r.read_string()?;
let _comment = r.read_string()?;
keys.push(key::parse_public_key(
key_blob,
Some(SignatureHash::SHA2_512),
)?);
}
}
Ok(keys)
}
/// Ask the agent to sign the supplied piece of data.
pub fn sign_request(
mut self,
public: &key::PublicKey,
mut data: CryptoVec,
) -> impl futures::Future<Output = (Self, Result<CryptoVec, Error>)> {
debug!("sign_request: {:?}", data);
let hash = self.prepare_sign_request(public, &data);
async move {
if let Err(e) = hash {
return (self, Err(e));
}
let resp = self.read_response().await;
debug!("resp = {:?}", &self.buf[..]);
if let Err(e) = resp {
return (self, Err(e));
}
#[allow(clippy::indexing_slicing, clippy::unwrap_used)]
// length is checked, hash already checked
if !self.buf.is_empty() && self.buf[0] == msg::SIGN_RESPONSE {
let resp = self.write_signature(hash.unwrap(), &mut data);
if let Err(e) = resp {
return (self, Err(e));
}
(self, Ok(data))
} else if self.buf.first() == Some(&msg::FAILURE) {
(self, Err(Error::AgentFailure))
} else {
debug!("self.buf = {:?}", &self.buf[..]);
(self, Ok(data))
}
}
}
fn prepare_sign_request(&mut self, public: &key::PublicKey, data: &[u8]) -> Result<u32, Error> {
self.buf.clear();
self.buf.resize(4);
self.buf.push(msg::SIGN_REQUEST);
key_blob(public, &mut self.buf)?;
self.buf.extend_ssh_string(data);
debug!("public = {:?}", public);
let hash = match public {
PublicKey::RSA { hash, .. } => match hash {
SignatureHash::SHA2_256 => 2,
SignatureHash::SHA2_512 => 4,
SignatureHash::SHA1 => 0,
},
_ => 0,
};
self.buf.push_u32_be(hash);
let len = self.buf.len() - 4;
BigEndian::write_u32(&mut self.buf[..], len as u32);
Ok(hash)
}
fn write_signature(&self, hash: u32, data: &mut CryptoVec) -> Result<(), Error> {
let mut r = self.buf.reader(1);
let mut resp = r.read_string()?.reader(0);
let t = resp.read_string()?;
if (hash == 2 && t == b"rsa-sha2-256") || (hash == 4 && t == b"rsa-sha2-512") || hash == 0 {
let sig = resp.read_string()?;
data.push_u32_be((t.len() + sig.len() + 8) as u32);
data.extend_ssh_string(t);
data.extend_ssh_string(sig);
}
Ok(())
}
/// Ask the agent to sign the supplied piece of data.
pub fn sign_request_base64(
mut self,
public: &key::PublicKey,
data: &[u8],
) -> impl futures::Future<Output = (Self, Result<String, Error>)> {
debug!("sign_request: {:?}", data);
let r = self.prepare_sign_request(public, data);
async move {
if let Err(e) = r {
return (self, Err(e));
}
let resp = self.read_response().await;
if let Err(e) = resp {
return (self, Err(e));
}
#[allow(clippy::indexing_slicing)] // length is checked
if !self.buf.is_empty() && self.buf[0] == msg::SIGN_RESPONSE {
let base64 = data_encoding::BASE64_NOPAD.encode(&self.buf[1..]);
(self, Ok(base64))
} else {
(self, Ok(String::new()))
}
}
}
/// Ask the agent to sign the supplied piece of data, and return a `Signature`.
pub fn sign_request_signature(
mut self,
public: &key::PublicKey,
data: &[u8],
) -> impl futures::Future<Output = (Self, Result<crate::signature::Signature, Error>)> {
debug!("sign_request: {:?}", data);
let r = self.prepare_sign_request(public, data);
async move {
if let Err(e) = r {
return (self, Err(e));
}
if let Err(e) = self.read_response().await {
return (self, Err(e));
}
#[allow(clippy::indexing_slicing)] // length is checked
if !self.buf.is_empty() && self.buf[0] == msg::SIGN_RESPONSE {
let as_sig = |buf: &CryptoVec| -> Result<crate::signature::Signature, Error> {
let mut r = buf.reader(1);
let mut resp = r.read_string()?.reader(0);
let typ = resp.read_string()?;
let sig = resp.read_string()?;
use crate::signature::Signature;
match typ {
b"ssh-rsa" => Ok(Signature::RSA {
bytes: sig.to_vec(),
hash: SignatureHash::SHA1,
}),
b"rsa-sha2-256" => Ok(Signature::RSA {
bytes: sig.to_vec(),
hash: SignatureHash::SHA2_256,
}),
b"rsa-sha2-512" => Ok(Signature::RSA {
bytes: sig.to_vec(),
hash: SignatureHash::SHA2_512,
}),
b"ssh-ed25519" => {
let mut sig_bytes = [0; 64];
sig_bytes.clone_from_slice(sig);
Ok(Signature::Ed25519(crate::signature::SignatureBytes(
sig_bytes,
)))
}
_ => Err(Error::UnknownSignatureType {
sig_type: std::str::from_utf8(typ).unwrap_or("").to_string(),
}),
}
};
let sig = as_sig(&self.buf);
(self, sig)
} else {
(self, Err(Error::AgentProtocolError))
}
}
}
/// Ask the agent to remove a key from its memory.
pub async fn remove_identity(&mut self, public: &key::PublicKey) -> Result<(), Error> {
self.buf.clear();
self.buf.resize(4);
self.buf.push(msg::REMOVE_IDENTITY);
key_blob(public, &mut self.buf)?;
let len = self.buf.len() - 4;
BigEndian::write_u32(&mut self.buf[..], len as u32);
self.read_response().await?;
Ok(())
}
/// Ask the agent to remove a smartcard from its memory.
pub async fn remove_smartcard_key(&mut self, id: &str, pin: &[u8]) -> Result<(), Error> {
self.buf.clear();
self.buf.resize(4);
self.buf.push(msg::REMOVE_SMARTCARD_KEY);
self.buf.extend_ssh_string(id.as_bytes());
self.buf.extend_ssh_string(pin);
let len = self.buf.len() - 4;
BigEndian::write_u32(&mut self.buf[..], len as u32);
self.read_response().await?;
Ok(())
}
/// Ask the agent to forget all known keys.
pub async fn remove_all_identities(&mut self) -> Result<(), Error> {
self.buf.clear();
self.buf.resize(4);
self.buf.push(msg::REMOVE_ALL_IDENTITIES);
BigEndian::write_u32(&mut self.buf[..], 1);
self.read_success().await?;
Ok(())
}
/// Send a custom message to the agent.
pub async fn extension(&mut self, typ: &[u8], ext: &[u8]) -> Result<(), Error> {
self.buf.clear();
self.buf.resize(4);
self.buf.push(msg::EXTENSION);
self.buf.extend_ssh_string(typ);
self.buf.extend_ssh_string(ext);
let len = self.buf.len() - 4;
BigEndian::write_u32(&mut self.buf[..], len as u32);
self.read_response().await?;
Ok(())
}
/// Ask the agent what extensions about supported extensions.
pub async fn query_extension(&mut self, typ: &[u8], mut ext: CryptoVec) -> Result<bool, Error> {
self.buf.clear();
self.buf.resize(4);
self.buf.push(msg::EXTENSION);
self.buf.extend_ssh_string(typ);
let len = self.buf.len() - 4;
BigEndian::write_u32(&mut self.buf[..], len as u32);
self.read_response().await?;
let mut r = self.buf.reader(1);
ext.extend(r.read_string()?);
#[allow(clippy::indexing_slicing)] // length is checked
Ok(!self.buf.is_empty() && self.buf[0] == msg::SUCCESS)
}
}
fn key_blob(public: &key::PublicKey, buf: &mut CryptoVec) -> Result<(), Error> {
match *public {
PublicKey::RSA { ref key, .. } => {
buf.extend(&[0, 0, 0, 0]);
let len0 = buf.len();
buf.extend_ssh_string(b"ssh-rsa");
buf.extend_ssh(&protocol::RsaPublicKey::from(key));
let len1 = buf.len();
#[allow(clippy::indexing_slicing)] // length is known
BigEndian::write_u32(&mut buf[5..], (len1 - len0) as u32);
}
PublicKey::Ed25519(ref p) => {
buf.extend(&[0, 0, 0, 0]);
let len0 = buf.len();
buf.extend_ssh_string(b"ssh-ed25519");
buf.extend_ssh_string(p.as_bytes());
let len1 = buf.len();
#[allow(clippy::indexing_slicing)] // length is known
BigEndian::write_u32(&mut buf[5..], (len1 - len0) as u32);
}
PublicKey::EC { .. } => {
buf.extend_ssh_string(&public.public_key_bytes());
}
}
Ok(())
}

16
vendor/russh-keys/src/agent/mod.rs vendored Normal file
View File

@@ -0,0 +1,16 @@
/// Write clients for SSH agents.
pub mod client;
mod msg;
/// Write servers for SSH agents.
pub mod server;
/// Constraints on how keys can be used
#[derive(Debug, PartialEq, Eq)]
pub enum Constraint {
/// The key shall disappear from the agent's memory after that many seconds.
KeyLifetime { seconds: u32 },
/// Signatures need to be confirmed by the agent (for instance using a dialog).
Confirm,
/// Custom constraints
Extensions { name: Vec<u8>, details: Vec<u8> },
}

23
vendor/russh-keys/src/agent/msg.rs vendored Normal file
View File

@@ -0,0 +1,23 @@
pub const FAILURE: u8 = 5;
pub const SUCCESS: u8 = 6;
pub const IDENTITIES_ANSWER: u8 = 12;
pub const SIGN_RESPONSE: u8 = 14;
// pub const EXTENSION_FAILURE: u8 = 28;
pub const REQUEST_IDENTITIES: u8 = 11;
pub const SIGN_REQUEST: u8 = 13;
pub const ADD_IDENTITY: u8 = 17;
pub const REMOVE_IDENTITY: u8 = 18;
pub const REMOVE_ALL_IDENTITIES: u8 = 19;
pub const ADD_ID_CONSTRAINED: u8 = 25;
pub const ADD_SMARTCARD_KEY: u8 = 20;
pub const REMOVE_SMARTCARD_KEY: u8 = 21;
pub const LOCK: u8 = 22;
pub const UNLOCK: u8 = 23;
pub const ADD_SMARTCARD_KEY_CONSTRAINED: u8 = 26;
pub const EXTENSION: u8 = 27;
pub const CONSTRAIN_LIFETIME: u8 = 1;
pub const CONSTRAIN_CONFIRM: u8 = 2;
// pub const CONSTRAIN_MAXSIGN: u8 = 3;
pub const CONSTRAIN_EXTENSION: u8 = 255;

341
vendor/russh-keys/src/agent/server.rs vendored Normal file
View File

@@ -0,0 +1,341 @@
use std::collections::HashMap;
use std::convert::TryFrom;
use std::marker::Sync;
use std::sync::{Arc, RwLock};
use std::time::{Duration, SystemTime};
use async_trait::async_trait;
use byteorder::{BigEndian, ByteOrder};
use futures::future::Future;
use futures::stream::{Stream, StreamExt};
use russh_cryptovec::CryptoVec;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::time::sleep;
use {std, tokio};
use super::{msg, Constraint};
use crate::encoding::{Encoding, Position, Reader};
use crate::{key, Error};
#[derive(Clone)]
#[allow(clippy::type_complexity)]
struct KeyStore(Arc<RwLock<HashMap<Vec<u8>, (Arc<key::KeyPair>, SystemTime, Vec<Constraint>)>>>);
#[derive(Clone)]
struct Lock(Arc<RwLock<CryptoVec>>);
#[allow(missing_docs)]
#[derive(Debug)]
pub enum ServerError<E> {
E(E),
Error(Error),
}
pub enum MessageType {
RequestKeys,
AddKeys,
RemoveKeys,
RemoveAllKeys,
Sign,
Lock,
Unlock,
}
#[async_trait]
pub trait Agent: Clone + Send + 'static {
fn confirm(
self,
_pk: Arc<key::KeyPair>,
) -> Box<dyn Future<Output = (Self, bool)> + Unpin + Send> {
Box::new(futures::future::ready((self, true)))
}
async fn confirm_request(&self, _msg: MessageType) -> bool {
true
}
}
pub async fn serve<S, L, A>(mut listener: L, agent: A) -> Result<(), Error>
where
S: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static,
L: Stream<Item = tokio::io::Result<S>> + Unpin,
A: Agent + Send + Sync + 'static,
{
let keys = KeyStore(Arc::new(RwLock::new(HashMap::new())));
let lock = Lock(Arc::new(RwLock::new(CryptoVec::new())));
while let Some(Ok(stream)) = listener.next().await {
let mut buf = CryptoVec::new();
buf.resize(4);
russh_util::runtime::spawn(
(Connection {
lock: lock.clone(),
keys: keys.clone(),
agent: Some(agent.clone()),
s: stream,
buf: CryptoVec::new(),
})
.run(),
);
}
Ok(())
}
impl Agent for () {
fn confirm(
self,
_: Arc<key::KeyPair>,
) -> Box<dyn Future<Output = (Self, bool)> + Unpin + Send> {
Box::new(futures::future::ready((self, true)))
}
}
struct Connection<S: AsyncRead + AsyncWrite + Send + 'static, A: Agent> {
lock: Lock,
keys: KeyStore,
agent: Option<A>,
s: S,
buf: CryptoVec,
}
impl<S: AsyncRead + AsyncWrite + Send + Unpin + 'static, A: Agent + Send + Sync + 'static>
Connection<S, A>
{
async fn run(mut self) -> Result<(), Error> {
let mut writebuf = CryptoVec::new();
loop {
// Reading the length
self.buf.clear();
self.buf.resize(4);
self.s.read_exact(&mut self.buf).await?;
// Reading the rest of the buffer
let len = BigEndian::read_u32(&self.buf) as usize;
self.buf.clear();
self.buf.resize(len);
self.s.read_exact(&mut self.buf).await?;
// respond
writebuf.clear();
self.respond(&mut writebuf).await?;
self.s.write_all(&writebuf).await?;
self.s.flush().await?
}
}
async fn respond(&mut self, writebuf: &mut CryptoVec) -> Result<(), Error> {
let is_locked = {
if let Ok(password) = self.lock.0.read() {
!password.is_empty()
} else {
true
}
};
writebuf.extend(&[0, 0, 0, 0]);
let mut r = self.buf.reader(0);
let agentref = self.agent.as_ref().ok_or(Error::AgentFailure)?;
match r.read_byte() {
Ok(11) if !is_locked && agentref.confirm_request(MessageType::RequestKeys).await => {
// request identities
if let Ok(keys) = self.keys.0.read() {
writebuf.push(msg::IDENTITIES_ANSWER);
writebuf.push_u32_be(keys.len() as u32);
for (k, _) in keys.iter() {
writebuf.extend_ssh_string(k);
writebuf.extend_ssh_string(b"");
}
} else {
writebuf.push(msg::FAILURE)
}
}
Ok(13) if !is_locked && agentref.confirm_request(MessageType::Sign).await => {
// sign request
let agent = self.agent.take().ok_or(Error::AgentFailure)?;
let (agent, signed) = self.try_sign(agent, r, writebuf).await?;
self.agent = Some(agent);
if signed {
return Ok(());
} else {
writebuf.resize(4);
writebuf.push(msg::FAILURE)
}
}
Ok(17) if !is_locked && agentref.confirm_request(MessageType::AddKeys).await => {
// add identity
if let Ok(true) = self.add_key(r, false, writebuf).await {
} else {
writebuf.push(msg::FAILURE)
}
}
Ok(18) if !is_locked && agentref.confirm_request(MessageType::RemoveKeys).await => {
// remove identity
if let Ok(true) = self.remove_identity(r) {
writebuf.push(msg::SUCCESS)
} else {
writebuf.push(msg::FAILURE)
}
}
Ok(19) if !is_locked && agentref.confirm_request(MessageType::RemoveAllKeys).await => {
// remove all identities
if let Ok(mut keys) = self.keys.0.write() {
keys.clear();
writebuf.push(msg::SUCCESS)
} else {
writebuf.push(msg::FAILURE)
}
}
Ok(22) if !is_locked && agentref.confirm_request(MessageType::Lock).await => {
// lock
if let Ok(()) = self.lock(r) {
writebuf.push(msg::SUCCESS)
} else {
writebuf.push(msg::FAILURE)
}
}
Ok(23) if is_locked && agentref.confirm_request(MessageType::Unlock).await => {
// unlock
if let Ok(true) = self.unlock(r) {
writebuf.push(msg::SUCCESS)
} else {
writebuf.push(msg::FAILURE)
}
}
Ok(25) if !is_locked && agentref.confirm_request(MessageType::AddKeys).await => {
// add identity constrained
if let Ok(true) = self.add_key(r, true, writebuf).await {
} else {
writebuf.push(msg::FAILURE)
}
}
_ => {
// Message not understood
writebuf.push(msg::FAILURE)
}
}
let len = writebuf.len() - 4;
BigEndian::write_u32(&mut writebuf[..], len as u32);
Ok(())
}
fn lock(&self, mut r: Position) -> Result<(), Error> {
let password = r.read_string()?;
let mut lock = self.lock.0.write().or(Err(Error::AgentFailure))?;
lock.extend(password);
Ok(())
}
fn unlock(&self, mut r: Position) -> Result<bool, Error> {
let password = r.read_string()?;
let mut lock = self.lock.0.write().or(Err(Error::AgentFailure))?;
if &lock[..] == password {
lock.clear();
Ok(true)
} else {
Ok(false)
}
}
fn remove_identity(&self, mut r: Position) -> Result<bool, Error> {
if let Ok(mut keys) = self.keys.0.write() {
if keys.remove(r.read_string()?).is_some() {
Ok(true)
} else {
Ok(false)
}
} else {
Ok(false)
}
}
async fn add_key(
&self,
mut r: Position<'_>,
constrained: bool,
writebuf: &mut CryptoVec,
) -> Result<bool, Error> {
let (blob, key_pair) = {
use ssh_encoding::{Decode, Encode};
let private_key = ssh_key::private::PrivateKey::new(
ssh_key::private::KeypairData::decode(&mut r)?,
"",
)?;
let _comment = r.read_string()?;
let key_pair = key::KeyPair::try_from(&private_key)?;
let mut blob = Vec::new();
private_key.public_key().key_data().encode(&mut blob)?;
(blob, key_pair)
};
writebuf.push(msg::SUCCESS);
let mut w = self.keys.0.write().or(Err(Error::AgentFailure))?;
let now = SystemTime::now();
if constrained {
let mut c = Vec::new();
while let Ok(t) = r.read_byte() {
if t == msg::CONSTRAIN_LIFETIME {
let seconds = r.read_u32()?;
c.push(Constraint::KeyLifetime { seconds });
let blob = blob.clone();
let keys = self.keys.clone();
russh_util::runtime::spawn(async move {
sleep(Duration::from_secs(seconds as u64)).await;
if let Ok(mut keys) = keys.0.write() {
let delete = if let Some(&(_, time, _)) = keys.get(&blob) {
time == now
} else {
false
};
if delete {
keys.remove(&blob);
}
}
});
} else if t == msg::CONSTRAIN_CONFIRM {
c.push(Constraint::Confirm)
} else {
return Ok(false);
}
}
w.insert(blob, (Arc::new(key_pair), now, c));
} else {
w.insert(blob, (Arc::new(key_pair), now, Vec::new()));
}
Ok(true)
}
async fn try_sign(
&self,
agent: A,
mut r: Position<'_>,
writebuf: &mut CryptoVec,
) -> Result<(A, bool), Error> {
let mut needs_confirm = false;
let key = {
let blob = r.read_string()?;
let k = self.keys.0.read().or(Err(Error::AgentFailure))?;
if let Some((key, _, constraints)) = k.get(blob) {
if constraints.iter().any(|c| *c == Constraint::Confirm) {
needs_confirm = true;
}
key.clone()
} else {
return Ok((agent, false));
}
};
let agent = if needs_confirm {
let (agent, ok) = agent.confirm(key.clone()).await;
if !ok {
return Ok((agent, false));
}
agent
} else {
agent
};
writebuf.push(msg::SIGN_RESPONSE);
let data = r.read_string()?;
key.add_signature(writebuf, data)?;
let len = writebuf.len();
BigEndian::write_u32(writebuf, (len - 4) as u32);
Ok((agent, true))
}
}

209
vendor/russh-keys/src/backend_openssl.rs vendored Normal file
View File

@@ -0,0 +1,209 @@
use std::convert::TryFrom;
use openssl::bn::{BigNum, BigNumContext, BigNumRef};
use openssl::hash::MessageDigest;
use openssl::pkey::{PKey, Private, Public};
use openssl::rsa::Rsa;
use crate::key::{RsaCrtExtra, SignatureHash};
use crate::{protocol, Error};
#[derive(Clone)]
pub struct RsaPublic {
key: Rsa<Public>,
pkey: PKey<Public>,
}
impl RsaPublic {
pub fn verify_detached(&self, hash: &SignatureHash, msg: &[u8], sig: &[u8]) -> bool {
openssl::sign::Verifier::new(message_digest_for(hash), &self.pkey)
.and_then(|mut v| v.verify_oneshot(sig, msg))
.unwrap_or(false)
}
}
impl TryFrom<&protocol::RsaPublicKey<'_>> for RsaPublic {
type Error = Error;
fn try_from(pk: &protocol::RsaPublicKey<'_>) -> Result<Self, Self::Error> {
let key = Rsa::from_public_components(
BigNum::from_slice(&pk.modulus)?,
BigNum::from_slice(&pk.public_exponent)?,
)?;
Ok(Self {
pkey: PKey::from_rsa(key.clone())?,
key,
})
}
}
impl<'a> From<&RsaPublic> for protocol::RsaPublicKey<'a> {
fn from(key: &RsaPublic) -> Self {
Self {
modulus: key.key.n().to_vec().into(),
public_exponent: key.key.e().to_vec().into(),
}
}
}
impl PartialEq for RsaPublic {
fn eq(&self, b: &RsaPublic) -> bool {
self.pkey.public_eq(&b.pkey)
}
}
impl Eq for RsaPublic {}
impl std::fmt::Debug for RsaPublic {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "RsaPublic {{ (hidden) }}")
}
}
#[derive(Clone)]
pub struct RsaPrivate {
key: Rsa<Private>,
pkey: PKey<Private>,
}
impl RsaPrivate {
pub fn new(
sk: &protocol::RsaPrivateKey<'_>,
extra: Option<&RsaCrtExtra<'_>>,
) -> Result<Self, Error> {
let (d, p, q) = (
BigNum::from_slice(&sk.private_exponent)?,
BigNum::from_slice(&sk.prime1)?,
BigNum::from_slice(&sk.prime2)?,
);
let (dp, dq) = if let Some(extra) = extra {
(
BigNum::from_slice(&extra.dp)?,
BigNum::from_slice(&extra.dq)?,
)
} else {
calc_dp_dq(d.as_ref(), p.as_ref(), q.as_ref())?
};
let key = Rsa::from_private_components(
BigNum::from_slice(&sk.public_key.modulus)?,
BigNum::from_slice(&sk.public_key.public_exponent)?,
d,
p,
q,
dp,
dq,
BigNum::from_slice(&sk.coefficient)?,
)?;
key.check_key()?;
Ok(Self {
pkey: PKey::from_rsa(key.clone())?,
key,
})
}
pub fn new_from_der(der: &[u8]) -> Result<Self, Error> {
let key = Rsa::private_key_from_der(der)?;
key.check_key()?;
Ok(Self {
pkey: PKey::from_rsa(key.clone())?,
key,
})
}
pub fn generate(bits: usize) -> Result<Self, Error> {
let key = Rsa::generate(bits as u32)?;
Ok(Self {
pkey: PKey::from_rsa(key.clone())?,
key,
})
}
pub fn sign(&self, hash: &SignatureHash, msg: &[u8]) -> Result<Vec<u8>, Error> {
Ok(
openssl::sign::Signer::new(message_digest_for(hash), &self.pkey)?
.sign_oneshot_to_vec(msg)?,
)
}
}
impl<'a> TryFrom<&RsaPrivate> for protocol::RsaPrivateKey<'a> {
type Error = Error;
fn try_from(key: &RsaPrivate) -> Result<protocol::RsaPrivateKey<'a>, Self::Error> {
let key = &key.key;
// We always set these.
if let (Some(p), Some(q), Some(iqmp)) = (key.p(), key.q(), key.iqmp()) {
Ok(protocol::RsaPrivateKey {
public_key: protocol::RsaPublicKey {
modulus: key.n().to_vec().into(),
public_exponent: key.e().to_vec().into(),
},
private_exponent: key.d().to_vec().into(),
prime1: p.to_vec().into(),
prime2: q.to_vec().into(),
coefficient: iqmp.to_vec().into(),
comment: b"".as_slice().into(),
})
} else {
Err(Error::KeyIsCorrupt)
}
}
}
impl<'a> TryFrom<&RsaPrivate> for RsaCrtExtra<'a> {
type Error = Error;
fn try_from(key: &RsaPrivate) -> Result<RsaCrtExtra<'a>, Self::Error> {
let key = &key.key;
// We always set these.
if let (Some(dp), Some(dq)) = (key.dmp1(), key.dmq1()) {
Ok(RsaCrtExtra {
dp: dp.to_vec().into(),
dq: dq.to_vec().into(),
})
} else {
Err(Error::KeyIsCorrupt)
}
}
}
impl<'a> From<&RsaPrivate> for protocol::RsaPublicKey<'a> {
fn from(key: &RsaPrivate) -> Self {
Self {
modulus: key.key.n().to_vec().into(),
public_exponent: key.key.e().to_vec().into(),
}
}
}
impl TryFrom<&RsaPrivate> for RsaPublic {
type Error = Error;
fn try_from(key: &RsaPrivate) -> Result<Self, Self::Error> {
let key = Rsa::from_public_components(key.key.n().to_owned()?, key.key.e().to_owned()?)?;
Ok(Self {
pkey: PKey::from_rsa(key.clone())?,
key,
})
}
}
fn message_digest_for(hash: &SignatureHash) -> MessageDigest {
match hash {
SignatureHash::SHA2_256 => MessageDigest::sha256(),
SignatureHash::SHA2_512 => MessageDigest::sha512(),
SignatureHash::SHA1 => MessageDigest::sha1(),
}
}
fn calc_dp_dq(d: &BigNumRef, p: &BigNumRef, q: &BigNumRef) -> Result<(BigNum, BigNum), Error> {
let one = BigNum::from_u32(1)?;
let p1 = p - one.as_ref();
let q1 = q - one.as_ref();
let mut context = BigNumContext::new()?;
let mut dp = BigNum::new()?;
let mut dq = BigNum::new()?;
dp.checked_rem(d, &p1, &mut context)?;
dq.checked_rem(d, &q1, &mut context)?;
Ok((dp, dq))
}

184
vendor/russh-keys/src/backend_rust.rs vendored Normal file
View File

@@ -0,0 +1,184 @@
use std::convert::TryFrom;
use rsa::traits::{PrivateKeyParts, PublicKeyParts};
use rsa::BigUint;
use crate::key::{RsaCrtExtra, SignatureHash};
use crate::{protocol, Error};
#[derive(Clone, PartialEq, Eq)]
pub struct RsaPublic {
key: rsa::RsaPublicKey,
}
impl RsaPublic {
pub fn verify_detached(&self, hash: &SignatureHash, msg: &[u8], sig: &[u8]) -> bool {
self.key
.verify(signature_scheme_for_hash(hash), &hash_msg(hash, msg), sig)
.is_ok()
}
}
impl TryFrom<&protocol::RsaPublicKey<'_>> for RsaPublic {
type Error = Error;
fn try_from(pk: &protocol::RsaPublicKey<'_>) -> Result<Self, Self::Error> {
Ok(Self {
key: rsa::RsaPublicKey::new(
BigUint::from_bytes_be(&pk.modulus),
BigUint::from_bytes_be(&pk.public_exponent),
)?,
})
}
}
impl<'a> From<&RsaPublic> for protocol::RsaPublicKey<'a> {
fn from(key: &RsaPublic) -> Self {
Self {
modulus: key.key.n().to_bytes_be().into(),
public_exponent: key.key.e().to_bytes_be().into(),
}
}
}
impl std::fmt::Debug for RsaPublic {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "RsaPublic {{ (hidden) }}")
}
}
#[derive(Clone)]
pub struct RsaPrivate {
key: rsa::RsaPrivateKey,
}
impl RsaPrivate {
pub fn new(
sk: &protocol::RsaPrivateKey<'_>,
extra: Option<&RsaCrtExtra<'_>>,
) -> Result<Self, Error> {
let mut key = rsa::RsaPrivateKey::from_components(
BigUint::from_bytes_be(&sk.public_key.modulus),
BigUint::from_bytes_be(&sk.public_key.public_exponent),
BigUint::from_bytes_be(&sk.private_exponent),
vec![
BigUint::from_bytes_be(&sk.prime1),
BigUint::from_bytes_be(&sk.prime2),
],
)?;
key.validate()?;
key.precompute()?;
if Some(BigUint::from_bytes_be(&sk.coefficient)) != key.crt_coefficient() {
return Err(Error::KeyIsCorrupt);
}
if let Some(extra) = extra {
if (
Some(&BigUint::from_bytes_be(&extra.dp)),
Some(&BigUint::from_bytes_be(&extra.dq)),
) != (key.dp(), key.dq())
{
return Err(Error::KeyIsCorrupt);
}
}
Ok(Self { key })
}
pub fn new_from_der(der: &[u8]) -> Result<Self, Error> {
use pkcs1::DecodeRsaPrivateKey;
Ok(Self {
key: rsa::RsaPrivateKey::from_pkcs1_der(der)?,
})
}
pub fn generate(bits: usize) -> Result<Self, Error> {
Ok(Self {
key: rsa::RsaPrivateKey::new(&mut crate::key::safe_rng(), bits)?,
})
}
pub fn sign(&self, hash: &SignatureHash, msg: &[u8]) -> Result<Vec<u8>, Error> {
Ok(self
.key
.sign(signature_scheme_for_hash(hash), &hash_msg(hash, msg))?)
}
}
impl<'a> TryFrom<&RsaPrivate> for protocol::RsaPrivateKey<'a> {
type Error = Error;
fn try_from(key: &RsaPrivate) -> Result<protocol::RsaPrivateKey<'a>, Self::Error> {
let key = &key.key;
// We always precompute these.
if let ([p, q], Some(iqmp)) = (key.primes(), key.crt_coefficient()) {
Ok(protocol::RsaPrivateKey {
public_key: protocol::RsaPublicKey {
modulus: key.n().to_bytes_be().into(),
public_exponent: key.e().to_bytes_be().into(),
},
private_exponent: key.d().to_bytes_be().into(),
prime1: p.to_bytes_be().into(),
prime2: q.to_bytes_be().into(),
coefficient: iqmp.to_bytes_be().into(),
comment: b"".as_slice().into(),
})
} else {
Err(Error::KeyIsCorrupt)
}
}
}
impl<'a> TryFrom<&RsaPrivate> for RsaCrtExtra<'a> {
type Error = Error;
fn try_from(key: &RsaPrivate) -> Result<RsaCrtExtra<'a>, Self::Error> {
let key = &key.key;
// We always precompute these.
if let (Some(dp), Some(dq)) = (key.dp(), key.dq()) {
Ok(RsaCrtExtra {
dp: dp.to_bytes_be().into(),
dq: dq.to_bytes_be().into(),
})
} else {
Err(Error::KeyIsCorrupt)
}
}
}
impl<'a> From<&RsaPrivate> for protocol::RsaPublicKey<'a> {
fn from(key: &RsaPrivate) -> Self {
Self {
modulus: key.key.n().to_bytes_be().into(),
public_exponent: key.key.e().to_bytes_be().into(),
}
}
}
impl TryFrom<&RsaPrivate> for RsaPublic {
type Error = Error;
fn try_from(key: &RsaPrivate) -> Result<Self, Self::Error> {
Ok(Self {
key: key.key.to_public_key(),
})
}
}
fn signature_scheme_for_hash(hash: &SignatureHash) -> rsa::pkcs1v15::Pkcs1v15Sign {
use rsa::pkcs1v15::Pkcs1v15Sign;
match *hash {
SignatureHash::SHA2_256 => Pkcs1v15Sign::new::<sha2::Sha256>(),
SignatureHash::SHA2_512 => Pkcs1v15Sign::new::<sha2::Sha512>(),
SignatureHash::SHA1 => Pkcs1v15Sign::new::<sha1::Sha1>(),
}
}
fn hash_msg(hash: &SignatureHash, msg: &[u8]) -> Vec<u8> {
use digest::Digest;
match *hash {
SignatureHash::SHA2_256 => sha2::Sha256::digest(msg).to_vec(),
SignatureHash::SHA2_512 => sha2::Sha512::digest(msg).to_vec(),
SignatureHash::SHA1 => sha1::Sha1::digest(msg).to_vec(),
}
}

263
vendor/russh-keys/src/ec.rs vendored Normal file
View File

@@ -0,0 +1,263 @@
use elliptic_curve::{Curve, CurveArithmetic, FieldBytes, FieldBytesSize};
use crate::key::safe_rng;
use crate::Error;
// p521::{SigningKey, VerifyingKey} are wrapped versions and do not provide PartialEq and Eq, hence
// we make our own type alias here.
mod local_p521 {
use rand_core::CryptoRngCore;
use sha2::{Digest, Sha512};
pub type NistP521 = p521::NistP521;
pub type VerifyingKey = ecdsa::VerifyingKey<NistP521>;
pub type SigningKey = ecdsa::SigningKey<NistP521>;
pub type Signature = ecdsa::Signature<NistP521>;
pub type Result<T> = ecdsa::Result<T>;
// Implement signing because p521::NistP521 does not implement DigestPrimitive trait.
pub fn try_sign_with_rng(
key: &SigningKey,
rng: &mut impl CryptoRngCore,
msg: &[u8],
) -> Result<Signature> {
use ecdsa::hazmat::{bits2field, sign_prehashed};
use elliptic_curve::Field;
let prehash = Sha512::digest(msg);
let z = bits2field::<NistP521>(&prehash)?;
let k = p521::Scalar::random(rng);
sign_prehashed(key.as_nonzero_scalar().as_ref(), k, &z).map(|sig| sig.0)
}
// Implement verifying because ecdsa::VerifyingKey<p521::NistP521> does not satisfy the trait
// bound requirements of the DigestVerifier's implementation in ecdsa crate.
pub fn verify(key: &VerifyingKey, msg: &[u8], signature: &Signature) -> Result<()> {
use ecdsa::signature::hazmat::PrehashVerifier;
key.verify_prehash(&Sha512::digest(msg), signature)
}
}
const CURVE_NISTP256: &str = "nistp256";
const CURVE_NISTP384: &str = "nistp384";
const CURVE_NISTP521: &str = "nistp521";
/// An ECC public key.
#[derive(Clone, Eq, PartialEq)]
pub enum PublicKey {
P256(p256::ecdsa::VerifyingKey),
P384(p384::ecdsa::VerifyingKey),
P521(local_p521::VerifyingKey),
}
impl PublicKey {
/// Returns the elliptic curve domain parameter identifiers defined in RFC 5656 section 6.1.
pub fn ident(&self) -> &'static str {
match self {
Self::P256(_) => CURVE_NISTP256,
Self::P384(_) => CURVE_NISTP384,
Self::P521(_) => CURVE_NISTP521,
}
}
/// Returns the ECC public key algorithm name defined in RFC 5656 section 6.2, in the form of
/// `"ecdsa-sha2-[identifier]"`.
pub fn algorithm(&self) -> &'static str {
match self {
Self::P256(_) => crate::ECDSA_SHA2_NISTP256,
Self::P384(_) => crate::ECDSA_SHA2_NISTP384,
Self::P521(_) => crate::ECDSA_SHA2_NISTP521,
}
}
/// Creates a `PrivateKey` from algorithm name and SEC1-encoded point on curve.
pub fn from_sec1_bytes(algorithm: &[u8], bytes: &[u8]) -> Result<Self, Error> {
match algorithm {
crate::KEYTYPE_ECDSA_SHA2_NISTP256 => Ok(Self::P256(
p256::ecdsa::VerifyingKey::from_sec1_bytes(bytes)?,
)),
crate::KEYTYPE_ECDSA_SHA2_NISTP384 => Ok(Self::P384(
p384::ecdsa::VerifyingKey::from_sec1_bytes(bytes)?,
)),
crate::KEYTYPE_ECDSA_SHA2_NISTP521 => Ok(Self::P521(
local_p521::VerifyingKey::from_sec1_bytes(bytes)?,
)),
_ => Err(Error::UnsupportedKeyType {
key_type_string: String::from_utf8(algorithm.to_vec())
.unwrap_or_else(|_| format!("{algorithm:?}")),
key_type_raw: algorithm.to_vec(),
}),
}
}
/// Returns the SEC1-encoded public curve point.
pub fn to_sec1_bytes(&self) -> Vec<u8> {
match self {
Self::P256(key) => key.to_encoded_point(false).as_bytes().to_vec(),
Self::P384(key) => key.to_encoded_point(false).as_bytes().to_vec(),
Self::P521(key) => key.to_encoded_point(false).as_bytes().to_vec(),
}
}
/// Verifies message against signature `(r, s)` using the associated digest algorithm.
pub fn verify(&self, msg: &[u8], r: &[u8], s: &[u8]) -> Result<(), Error> {
use ecdsa::signature::Verifier;
match self {
Self::P256(key) => {
key.verify(msg, &signature_from_scalar_bytes::<p256::NistP256>(r, s)?)
}
Self::P384(key) => {
key.verify(msg, &signature_from_scalar_bytes::<p384::NistP384>(r, s)?)
}
Self::P521(key) => local_p521::verify(
key,
msg,
&signature_from_scalar_bytes::<p521::NistP521>(r, s)?,
),
}
.map_err(Error::from)
}
}
impl std::fmt::Debug for PublicKey {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
Self::P256(_) => write!(f, "P256"),
Self::P384(_) => write!(f, "P384"),
Self::P521(_) => write!(f, "P521"),
}
}
}
/// An ECC private key.
#[derive(Clone, Eq, PartialEq)]
pub enum PrivateKey {
P256(p256::ecdsa::SigningKey),
P384(p384::ecdsa::SigningKey),
P521(local_p521::SigningKey),
}
impl PrivateKey {
/// Creates a `PrivateKey` with algorithm name and scalar.
pub fn new_from_secret_scalar(algorithm: &[u8], scalar: &[u8]) -> Result<Self, Error> {
match algorithm {
crate::KEYTYPE_ECDSA_SHA2_NISTP256 => {
Ok(Self::P256(p256::ecdsa::SigningKey::from_slice(scalar)?))
}
crate::KEYTYPE_ECDSA_SHA2_NISTP384 => {
Ok(Self::P384(p384::ecdsa::SigningKey::from_slice(scalar)?))
}
crate::KEYTYPE_ECDSA_SHA2_NISTP521 => {
Ok(Self::P521(local_p521::SigningKey::from_slice(scalar)?))
}
_ => Err(Error::UnsupportedKeyType {
key_type_string: String::from_utf8(algorithm.to_vec())
.unwrap_or_else(|_| format!("{algorithm:?}")),
key_type_raw: algorithm.to_vec(),
}),
}
}
/// Returns the elliptic curve domain parameter identifiers defined in RFC 5656 section 6.1.
pub fn ident(&self) -> &'static str {
match self {
Self::P256(_) => CURVE_NISTP256,
Self::P384(_) => CURVE_NISTP384,
Self::P521(_) => CURVE_NISTP521,
}
}
/// Returns the ECC public key algorithm name defined in RFC 5656 section 6.2, in the form of
/// `"ecdsa-sha2-[identifier]"`.
pub fn algorithm(&self) -> &'static str {
match self {
Self::P256(_) => crate::ECDSA_SHA2_NISTP256,
Self::P384(_) => crate::ECDSA_SHA2_NISTP384,
Self::P521(_) => crate::ECDSA_SHA2_NISTP521,
}
}
/// Returns the public key.
pub fn to_public_key(&self) -> PublicKey {
match self {
Self::P256(key) => PublicKey::P256(*key.verifying_key()),
Self::P384(key) => PublicKey::P384(*key.verifying_key()),
Self::P521(key) => PublicKey::P521(*key.verifying_key()),
}
}
/// Returns the secret scalar in bytes.
pub fn to_secret_bytes(&self) -> Vec<u8> {
match self {
Self::P256(key) => key.to_bytes().to_vec(),
Self::P384(key) => key.to_bytes().to_vec(),
Self::P521(key) => key.to_bytes().to_vec(),
}
}
/// Sign the message with associated digest algorithm.
pub fn try_sign(&self, msg: &[u8]) -> Result<(Vec<u8>, Vec<u8>), Error> {
use ecdsa::signature::RandomizedSigner;
Ok(match self {
Self::P256(key) => {
signature_to_scalar_bytes(key.try_sign_with_rng(&mut safe_rng(), msg)?)
}
Self::P384(key) => {
signature_to_scalar_bytes(key.try_sign_with_rng(&mut safe_rng(), msg)?)
}
Self::P521(key) => {
signature_to_scalar_bytes(local_p521::try_sign_with_rng(key, &mut safe_rng(), msg)?)
}
})
}
}
impl std::fmt::Debug for PrivateKey {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
Self::P256(_) => write!(f, "P256 {{ (hidden) }}"),
Self::P384(_) => write!(f, "P384 {{ (hidden) }}"),
Self::P521(_) => write!(f, "P521 {{ (hidden) }}"),
}
}
}
fn try_field_bytes_from_mpint<C>(b: &[u8]) -> Option<FieldBytes<C>>
where
C: Curve + CurveArithmetic,
{
use typenum::Unsigned;
let size = FieldBytesSize::<C>::to_usize();
assert!(size > 0);
#[allow(clippy::indexing_slicing)] // Length checked
if b.len() == size + 1 && b[0] == 0 {
Some(FieldBytes::<C>::clone_from_slice(&b[1..]))
} else if b.len() == size {
Some(FieldBytes::<C>::clone_from_slice(b))
} else if b.len() < size {
let mut fb: FieldBytes<C> = Default::default();
fb.as_mut_slice()[size - b.len()..].clone_from_slice(b);
Some(fb)
} else {
None
}
}
fn signature_from_scalar_bytes<C>(r: &[u8], s: &[u8]) -> Result<ecdsa::Signature<C>, Error>
where
C: Curve + CurveArithmetic + elliptic_curve::PrimeCurve,
ecdsa::SignatureSize<C>: elliptic_curve::generic_array::ArrayLength<u8>,
{
Ok(ecdsa::Signature::<C>::from_scalars(
try_field_bytes_from_mpint::<C>(r).ok_or(Error::InvalidSignature)?,
try_field_bytes_from_mpint::<C>(s).ok_or(Error::InvalidSignature)?,
)?)
}
fn signature_to_scalar_bytes<C>(sig: ecdsa::Signature<C>) -> (Vec<u8>, Vec<u8>)
where
C: Curve + CurveArithmetic + elliptic_curve::PrimeCurve,
ecdsa::SignatureSize<C>: elliptic_curve::generic_array::ArrayLength<u8>,
{
let (r, s) = sig.split_bytes();
(r.to_vec(), s.to_vec())
}

314
vendor/russh-keys/src/encoding.rs vendored Normal file
View File

@@ -0,0 +1,314 @@
// Copyright 2016 Pierre-Étienne Meunier
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use russh_cryptovec::CryptoVec;
use crate::Error;
#[doc(hidden)]
pub trait Bytes {
fn bytes(&self) -> &[u8];
}
impl<A: AsRef<str>> Bytes for A {
fn bytes(&self) -> &[u8] {
self.as_ref().as_bytes()
}
}
/// Encode in the SSH format.
pub trait Encoding {
/// Push an SSH-encoded string to `self`.
fn extend_ssh_string(&mut self, s: &[u8]);
/// Push an SSH-encoded blank string of length `s` to `self`.
fn extend_ssh_string_blank(&mut self, s: usize) -> &mut [u8];
/// Push an SSH-encoded multiple-precision integer.
fn extend_ssh_mpint(&mut self, s: &[u8]);
/// Push an SSH-encoded list.
fn extend_list<A: Bytes, I: Iterator<Item = A>>(&mut self, list: I);
/// Push an SSH-encoded empty list.
fn write_empty_list(&mut self);
/// Push an SSH-encoded value.
fn extend_ssh<T: SshWrite>(&mut self, v: &T) {
v.write_ssh(self)
}
/// Push a nested SSH-encoded value.
fn extend_wrapped<F>(&mut self, write: F)
where
F: FnOnce(&mut Self);
}
/// Trait for writing value in SSH-encoded format.
pub trait SshWrite {
/// Write the value.
fn write_ssh<E: Encoding + ?Sized>(&self, encoder: &mut E);
}
/// Encoding length of the given mpint.
#[allow(clippy::indexing_slicing)]
pub fn mpint_len(s: &[u8]) -> usize {
let mut i = 0;
while i < s.len() && s[i] == 0 {
i += 1
}
(if s[i] & 0x80 != 0 { 5 } else { 4 }) + s.len() - i
}
impl Encoding for Vec<u8> {
#[allow(clippy::unwrap_used)] // writing into Vec<> can't panic
fn extend_ssh_string(&mut self, s: &[u8]) {
self.write_u32::<BigEndian>(s.len() as u32).unwrap();
self.extend(s);
}
#[allow(clippy::unwrap_used)] // writing into Vec<> can't panic
fn extend_ssh_string_blank(&mut self, len: usize) -> &mut [u8] {
self.write_u32::<BigEndian>(len as u32).unwrap();
let current = self.len();
self.resize(current + len, 0u8);
#[allow(clippy::indexing_slicing)] // length is known
&mut self[current..]
}
#[allow(clippy::unwrap_used)] // writing into Vec<> can't panic
#[allow(clippy::indexing_slicing)] // length is known
fn extend_ssh_mpint(&mut self, s: &[u8]) {
// Skip initial 0s.
let mut i = 0;
while i < s.len() && s[i] == 0 {
i += 1
}
// If the first non-zero is >= 128, write its length (u32, BE), followed by 0.
if s[i] & 0x80 != 0 {
self.write_u32::<BigEndian>((s.len() - i + 1) as u32)
.unwrap();
self.push(0)
} else {
self.write_u32::<BigEndian>((s.len() - i) as u32).unwrap();
}
self.extend(&s[i..]);
}
#[allow(clippy::indexing_slicing)] // length is known
fn extend_list<A: Bytes, I: Iterator<Item = A>>(&mut self, list: I) {
let len0 = self.len();
self.extend([0, 0, 0, 0]);
let mut first = true;
for i in list {
if !first {
self.push(b',')
} else {
first = false;
}
self.extend(i.bytes())
}
let len = (self.len() - len0 - 4) as u32;
BigEndian::write_u32(&mut self[len0..], len);
}
fn write_empty_list(&mut self) {
self.extend([0, 0, 0, 0]);
}
fn extend_wrapped<F>(&mut self, write: F)
where
F: FnOnce(&mut Self),
{
let len_offset = self.len();
#[allow(clippy::unwrap_used)] // writing into Vec<> can't panic
self.write_u32::<BigEndian>(0).unwrap();
let data_offset = self.len();
write(self);
let data_len = self.len() - data_offset;
#[allow(clippy::indexing_slicing)] // length is known
BigEndian::write_u32(&mut self[len_offset..], data_len as u32);
}
}
impl Encoding for CryptoVec {
fn extend_ssh_string(&mut self, s: &[u8]) {
self.push_u32_be(s.len() as u32);
self.extend(s);
}
#[allow(clippy::indexing_slicing)] // length is known
fn extend_ssh_string_blank(&mut self, len: usize) -> &mut [u8] {
self.push_u32_be(len as u32);
let current = self.len();
self.resize(current + len);
&mut self[current..]
}
#[allow(clippy::indexing_slicing)] // length is known
fn extend_ssh_mpint(&mut self, s: &[u8]) {
// Skip initial 0s.
let mut i = 0;
while i < s.len() && s[i] == 0 {
i += 1
}
// If the first non-zero is >= 128, write its length (u32, BE), followed by 0.
if s[i] & 0x80 != 0 {
self.push_u32_be((s.len() - i + 1) as u32);
self.push(0)
} else {
self.push_u32_be((s.len() - i) as u32);
}
self.extend(&s[i..]);
}
fn extend_list<A: Bytes, I: Iterator<Item = A>>(&mut self, list: I) {
let len0 = self.len();
self.extend(&[0, 0, 0, 0]);
let mut first = true;
for i in list {
if !first {
self.push(b',')
} else {
first = false;
}
self.extend(i.bytes())
}
let len = (self.len() - len0 - 4) as u32;
#[allow(clippy::indexing_slicing)] // length is known
BigEndian::write_u32(&mut self[len0..], len);
}
fn write_empty_list(&mut self) {
self.extend(&[0, 0, 0, 0]);
}
fn extend_wrapped<F>(&mut self, write: F)
where
F: FnOnce(&mut Self),
{
let len_offset = self.len();
self.push_u32_be(0);
let data_offset = self.len();
write(self);
let data_len = self.len() - data_offset;
#[allow(clippy::indexing_slicing)] // length is known
BigEndian::write_u32(&mut self[len_offset..], data_len as u32);
}
}
/// A cursor-like trait to read SSH-encoded things.
pub trait Reader {
/// Create an SSH reader for `self`.
fn reader(&self, starting_at: usize) -> Position;
}
impl Reader for CryptoVec {
fn reader(&self, starting_at: usize) -> Position {
Position {
s: self,
position: starting_at,
}
}
}
impl Reader for [u8] {
fn reader(&self, starting_at: usize) -> Position {
Position {
s: self,
position: starting_at,
}
}
}
/// A cursor-like type to read SSH-encoded values.
#[derive(Debug)]
pub struct Position<'a> {
s: &'a [u8],
#[doc(hidden)]
pub position: usize,
}
impl<'a> Position<'a> {
/// Read one string from this reader.
pub fn read_string(&mut self) -> Result<&'a [u8], Error> {
let len = self.read_u32()? as usize;
if self.position + len <= self.s.len() {
#[allow(clippy::indexing_slicing)] // length is known
let result = &self.s[self.position..(self.position + len)];
self.position += len;
Ok(result)
} else {
Err(Error::IndexOutOfBounds)
}
}
/// Read a `u32` from this reader.
pub fn read_u32(&mut self) -> Result<u32, Error> {
if self.position + 4 <= self.s.len() {
#[allow(clippy::indexing_slicing)] // length is known
let u = BigEndian::read_u32(&self.s[self.position..]);
self.position += 4;
Ok(u)
} else {
Err(Error::IndexOutOfBounds)
}
}
/// Read one byte from this reader.
pub fn read_byte(&mut self) -> Result<u8, Error> {
if self.position < self.s.len() {
#[allow(clippy::indexing_slicing)] // length is known
let u = self.s[self.position];
self.position += 1;
Ok(u)
} else {
Err(Error::IndexOutOfBounds)
}
}
/// Read one byte from this reader.
pub fn read_mpint(&mut self) -> Result<&'a [u8], Error> {
let len = self.read_u32()? as usize;
if self.position + len <= self.s.len() {
#[allow(clippy::indexing_slicing)] // length was checked
let result = &self.s[self.position..(self.position + len)];
self.position += len;
Ok(result)
} else {
Err(Error::IndexOutOfBounds)
}
}
pub fn read_ssh<T: SshRead<'a>>(&mut self) -> Result<T, Error> {
T::read_ssh(self)
}
}
/// Trait for reading value in SSH-encoded format.
pub trait SshRead<'a>: Sized + 'a {
/// Read the value from a position.
fn read_ssh(pos: &mut Position<'a>) -> Result<Self, Error>;
}
impl<'a> ssh_encoding::Reader for Position<'a> {
fn read<'o>(&mut self, out: &'o mut [u8]) -> ssh_encoding::Result<&'o [u8]> {
out.copy_from_slice(
self.s
.get(self.position..(self.position + out.len()))
.ok_or(ssh_encoding::Error::Length)?,
);
self.position += out.len();
Ok(out)
}
fn remaining_len(&self) -> usize {
self.s.len() - self.position
}
}

131
vendor/russh-keys/src/format/mod.rs vendored Normal file
View File

@@ -0,0 +1,131 @@
use std::io::Write;
use data_encoding::{BASE64_MIME, HEXLOWER_PERMISSIVE};
use super::is_base64_char;
use crate::{key, Error};
pub mod openssh;
#[cfg(feature = "legacy-ed25519-pkcs8-parser")]
mod pkcs8_legacy;
pub use self::openssh::*;
pub mod pkcs5;
pub use self::pkcs5::*;
pub mod pkcs8;
const AES_128_CBC: &str = "DEK-Info: AES-128-CBC,";
#[derive(Clone, Copy, Debug)]
/// AES encryption key.
pub enum Encryption {
/// Key for AES128
Aes128Cbc([u8; 16]),
/// Key for AES256
Aes256Cbc([u8; 16]),
}
#[derive(Clone, Debug)]
enum Format {
Rsa,
Openssh,
Pkcs5Encrypted(Encryption),
Pkcs8Encrypted,
Pkcs8,
}
/// Decode a secret key, possibly deciphering it with the supplied
/// password.
pub fn decode_secret_key(secret: &str, password: Option<&str>) -> Result<key::KeyPair, Error> {
let mut format = None;
let secret = {
let mut started = false;
let mut sec = String::new();
for l in secret.lines() {
if started {
if l.starts_with("-----END ") {
break;
}
if l.chars().all(is_base64_char) {
sec.push_str(l)
} else if l.starts_with(AES_128_CBC) {
let iv_: Vec<u8> =
HEXLOWER_PERMISSIVE.decode(l.split_at(AES_128_CBC.len()).1.as_bytes())?;
if iv_.len() != 16 {
return Err(Error::CouldNotReadKey);
}
let mut iv = [0; 16];
iv.clone_from_slice(&iv_);
format = Some(Format::Pkcs5Encrypted(Encryption::Aes128Cbc(iv)))
}
}
if l == "-----BEGIN OPENSSH PRIVATE KEY-----" {
started = true;
format = Some(Format::Openssh);
} else if l == "-----BEGIN RSA PRIVATE KEY-----" {
started = true;
format = Some(Format::Rsa);
} else if l == "-----BEGIN ENCRYPTED PRIVATE KEY-----" {
started = true;
format = Some(Format::Pkcs8Encrypted);
} else if l == "-----BEGIN PRIVATE KEY-----" {
started = true;
format = Some(Format::Pkcs8);
}
}
sec
};
let secret = BASE64_MIME.decode(secret.as_bytes())?;
match format {
Some(Format::Openssh) => decode_openssh(&secret, password),
Some(Format::Rsa) => decode_rsa(&secret),
Some(Format::Pkcs5Encrypted(enc)) => decode_pkcs5(&secret, password, enc),
Some(Format::Pkcs8Encrypted) | Some(Format::Pkcs8) => {
let result = self::pkcs8::decode_pkcs8(&secret, password.map(|x| x.as_bytes()));
#[cfg(feature = "legacy-ed25519-pkcs8-parser")]
{
if result.is_err() {
let legacy_result =
pkcs8_legacy::decode_pkcs8(&secret, password.map(|x| x.as_bytes()));
if let Ok(key) = legacy_result {
return Ok(key);
}
}
}
result
}
None => Err(Error::CouldNotReadKey),
}
}
pub fn encode_pkcs8_pem<W: Write>(key: &key::KeyPair, mut w: W) -> Result<(), Error> {
let x = self::pkcs8::encode_pkcs8(key)?;
w.write_all(b"-----BEGIN PRIVATE KEY-----\n")?;
w.write_all(BASE64_MIME.encode(&x).as_bytes())?;
w.write_all(b"\n-----END PRIVATE KEY-----\n")?;
Ok(())
}
pub fn encode_pkcs8_pem_encrypted<W: Write>(
key: &key::KeyPair,
pass: &[u8],
rounds: u32,
mut w: W,
) -> Result<(), Error> {
let x = self::pkcs8::encode_pkcs8_encrypted(pass, rounds, key)?;
w.write_all(b"-----BEGIN ENCRYPTED PRIVATE KEY-----\n")?;
w.write_all(BASE64_MIME.encode(&x).as_bytes())?;
w.write_all(b"\n-----END ENCRYPTED PRIVATE KEY-----\n")?;
Ok(())
}
fn decode_rsa(secret: &[u8]) -> Result<key::KeyPair, Error> {
Ok(key::KeyPair::RSA {
key: crate::backend::RsaPrivate::new_from_der(secret)?,
hash: key::SignatureHash::SHA2_256,
})
}

121
vendor/russh-keys/src/format/openssh.rs vendored Normal file
View File

@@ -0,0 +1,121 @@
use std::convert::TryFrom;
use ssh_key::private::{
EcdsaKeypair, Ed25519Keypair, KeypairData, PrivateKey, RsaKeypair, RsaPrivateKey,
};
use ssh_key::public::{Ed25519PublicKey, KeyData, RsaPublicKey};
use ssh_key::{Algorithm, HashAlg};
use crate::key::{KeyPair, PublicKey, SignatureHash};
use crate::{ec, protocol, Error};
/// Decode a secret key given in the OpenSSH format, deciphering it if
/// needed using the supplied password.
pub fn decode_openssh(secret: &[u8], password: Option<&str>) -> Result<KeyPair, Error> {
let pk = PrivateKey::from_bytes(secret)?;
KeyPair::try_from(&match password {
Some(password) => pk.decrypt(password)?,
None => pk,
})
}
impl TryFrom<&PrivateKey> for KeyPair {
type Error = Error;
fn try_from(pk: &PrivateKey) -> Result<Self, Self::Error> {
match pk.key_data() {
KeypairData::Ed25519(Ed25519Keypair { public, private }) => {
let key = ed25519_dalek::SigningKey::from(private.as_ref());
let public_key = ed25519_dalek::VerifyingKey::from_bytes(public.as_ref())?;
if public_key != key.verifying_key() {
return Err(Error::KeyIsCorrupt);
}
Ok(KeyPair::Ed25519(key))
}
KeypairData::Rsa(keypair) => {
KeyPair::new_rsa_with_hash(&keypair.into(), None, SignatureHash::SHA2_512)
}
KeypairData::Ecdsa(keypair) => {
let key_type = match keypair {
EcdsaKeypair::NistP256 { .. } => crate::KEYTYPE_ECDSA_SHA2_NISTP256,
EcdsaKeypair::NistP384 { .. } => crate::KEYTYPE_ECDSA_SHA2_NISTP384,
EcdsaKeypair::NistP521 { .. } => crate::KEYTYPE_ECDSA_SHA2_NISTP521,
};
let key =
ec::PrivateKey::new_from_secret_scalar(key_type, keypair.private_key_bytes())?;
let public_key =
ec::PublicKey::from_sec1_bytes(key_type, keypair.public_key_bytes())?;
if public_key != key.to_public_key() {
return Err(Error::KeyIsCorrupt);
}
Ok(KeyPair::EC { key })
}
KeypairData::Encrypted(_) => Err(Error::KeyIsEncrypted),
_ => Err(Error::UnsupportedKeyType {
key_type_string: pk.algorithm().as_str().into(),
key_type_raw: pk.algorithm().as_str().as_bytes().into(),
}),
}
}
}
impl<'a> From<&'a RsaKeypair> for protocol::RsaPrivateKey<'a> {
fn from(key: &'a RsaKeypair) -> Self {
let RsaPublicKey { e, n } = &key.public;
let RsaPrivateKey { d, iqmp, p, q } = &key.private;
Self {
public_key: protocol::RsaPublicKey {
public_exponent: e.as_bytes().into(),
modulus: n.as_bytes().into(),
},
private_exponent: d.as_bytes().into(),
prime1: p.as_bytes().into(),
prime2: q.as_bytes().into(),
coefficient: iqmp.as_bytes().into(),
comment: b"".as_slice().into(),
}
}
}
impl TryFrom<&KeyData> for PublicKey {
type Error = Error;
fn try_from(key_data: &KeyData) -> Result<Self, Self::Error> {
match key_data {
KeyData::Ed25519(Ed25519PublicKey(public)) => Ok(PublicKey::Ed25519(
ed25519_dalek::VerifyingKey::from_bytes(public)?,
)),
KeyData::Rsa(ref public) => PublicKey::new_rsa_with_hash(
&public.into(),
match key_data.algorithm() {
Algorithm::Rsa { hash } => match hash {
Some(HashAlg::Sha256) => SignatureHash::SHA2_256,
Some(HashAlg::Sha512) => SignatureHash::SHA2_512,
_ => SignatureHash::SHA1,
},
_ => return Err(Error::KeyIsCorrupt),
},
),
KeyData::Ecdsa(public) => Ok(PublicKey::EC {
key: ec::PublicKey::from_sec1_bytes(
key_data.algorithm().as_str().as_bytes(),
public.as_sec1_bytes(),
)?,
}),
_ => Err(Error::UnsupportedKeyType {
key_type_string: key_data.algorithm().as_str().into(),
key_type_raw: key_data.algorithm().as_str().as_bytes().into(),
}),
}
}
}
impl<'a> From<&'a RsaPublicKey> for protocol::RsaPublicKey<'a> {
fn from(key: &'a RsaPublicKey) -> Self {
let RsaPublicKey { e, n } = key;
Self {
public_exponent: e.as_bytes().into(),
modulus: n.as_bytes().into(),
}
}
}

35
vendor/russh-keys/src/format/pkcs5.rs vendored Normal file
View File

@@ -0,0 +1,35 @@
use aes::*;
use super::Encryption;
use crate::{key, Error};
/// Decode a secret key in the PKCS#5 format, possibly deciphering it
/// using the supplied password.
pub fn decode_pkcs5(
secret: &[u8],
password: Option<&str>,
enc: Encryption,
) -> Result<key::KeyPair, Error> {
use aes::cipher::{BlockDecryptMut, KeyIvInit};
use block_padding::Pkcs7;
if let Some(pass) = password {
let sec = match enc {
Encryption::Aes128Cbc(ref iv) => {
let mut c = md5::Context::new();
c.consume(pass.as_bytes());
c.consume(&iv[..8]);
let md5 = c.compute();
#[allow(clippy::unwrap_used)] // AES parameters are static
let c = cbc::Decryptor::<Aes128>::new_from_slices(&md5.0, &iv[..]).unwrap();
let mut dec = secret.to_vec();
c.decrypt_padded_mut::<Pkcs7>(&mut dec)?.to_vec()
}
Encryption::Aes256Cbc(_) => unimplemented!(),
};
super::decode_rsa(&sec)
} else {
Err(Error::KeyIsEncrypted)
}
}

181
vendor/russh-keys/src/format/pkcs8.rs vendored Normal file
View File

@@ -0,0 +1,181 @@
use std::convert::{TryFrom, TryInto};
use pkcs8::{EncodePrivateKey, PrivateKeyInfo, SecretDocument};
use crate::key::SignatureHash;
use crate::{ec, key, protocol, Error};
/// Decode a PKCS#8-encoded private key.
pub fn decode_pkcs8(ciphertext: &[u8], password: Option<&[u8]>) -> Result<key::KeyPair, Error> {
let doc = SecretDocument::try_from(ciphertext)?;
let doc = if let Some(password) = password {
doc.decode_msg::<pkcs8::EncryptedPrivateKeyInfo>()?
.decrypt(password)?
} else {
doc
};
key::KeyPair::try_from(doc.decode_msg::<PrivateKeyInfo>()?)
}
impl<'a> TryFrom<PrivateKeyInfo<'a>> for key::KeyPair {
type Error = Error;
fn try_from(pki: PrivateKeyInfo<'a>) -> Result<Self, Self::Error> {
match pki.algorithm.oid {
ed25519_dalek::pkcs8::ALGORITHM_OID => Ok(key::KeyPair::Ed25519(
ed25519_dalek::pkcs8::KeypairBytes::try_from(pki)?
.secret_key
.into(),
)),
pkcs1::ALGORITHM_OID => {
let sk = &pkcs1::RsaPrivateKey::try_from(pki.private_key)?;
key::KeyPair::new_rsa_with_hash(
&sk.into(),
Some(&sk.into()),
SignatureHash::SHA2_256,
)
}
sec1::ALGORITHM_OID => Ok(key::KeyPair::EC {
key: pki.try_into()?,
}),
oid => Err(Error::UnknownAlgorithm(oid)),
}
}
}
impl<'a> From<&pkcs1::RsaPrivateKey<'a>> for protocol::RsaPrivateKey<'a> {
fn from(sk: &pkcs1::RsaPrivateKey<'a>) -> Self {
Self {
public_key: protocol::RsaPublicKey {
public_exponent: sk.public_exponent.as_bytes().into(),
modulus: sk.modulus.as_bytes().into(),
},
private_exponent: sk.private_exponent.as_bytes().into(),
prime1: sk.prime1.as_bytes().into(),
prime2: sk.prime2.as_bytes().into(),
coefficient: sk.coefficient.as_bytes().into(),
comment: b"".as_slice().into(),
}
}
}
impl<'a> From<&pkcs1::RsaPrivateKey<'a>> for key::RsaCrtExtra<'a> {
fn from(sk: &pkcs1::RsaPrivateKey<'a>) -> Self {
Self {
dp: sk.exponent1.as_bytes().into(),
dq: sk.exponent2.as_bytes().into(),
}
}
}
// Note: It's infeasible to implement `EncodePrivateKey` because that is bound to `pkcs8::Result`.
impl TryFrom<&key::RsaPrivate> for SecretDocument {
type Error = Error;
fn try_from(key: &key::RsaPrivate) -> Result<Self, Self::Error> {
use der::Encode;
use pkcs1::UintRef;
let sk = protocol::RsaPrivateKey::try_from(key)?;
let extra = key::RsaCrtExtra::try_from(key)?;
let rsa_private_key = pkcs1::RsaPrivateKey {
modulus: UintRef::new(&sk.public_key.modulus)?,
public_exponent: UintRef::new(&sk.public_key.public_exponent)?,
private_exponent: UintRef::new(&sk.private_exponent)?,
prime1: UintRef::new(&sk.prime1)?,
prime2: UintRef::new(&sk.prime2)?,
exponent1: UintRef::new(&extra.dp)?,
exponent2: UintRef::new(&extra.dq)?,
coefficient: UintRef::new(&sk.coefficient)?,
other_prime_infos: None,
};
let pki = PrivateKeyInfo {
algorithm: spki::AlgorithmIdentifier {
oid: pkcs1::ALGORITHM_OID,
parameters: Some(der::asn1::Null.into()),
},
private_key: &rsa_private_key.to_der()?,
public_key: None,
};
Ok(Self::try_from(pki)?)
}
}
impl TryFrom<PrivateKeyInfo<'_>> for ec::PrivateKey {
type Error = Error;
fn try_from(pki: PrivateKeyInfo<'_>) -> Result<Self, Self::Error> {
use pkcs8::AssociatedOid;
match pki.algorithm.parameters_oid()? {
p256::NistP256::OID => Ok(ec::PrivateKey::P256(pki.try_into()?)),
p384::NistP384::OID => Ok(ec::PrivateKey::P384(pki.try_into()?)),
p521::NistP521::OID => Ok(ec::PrivateKey::P521(pki.try_into()?)),
oid => Err(Error::UnknownAlgorithm(oid)),
}
}
}
impl EncodePrivateKey for ec::PrivateKey {
fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
match self {
ec::PrivateKey::P256(key) => key.to_pkcs8_der(),
ec::PrivateKey::P384(key) => key.to_pkcs8_der(),
ec::PrivateKey::P521(key) => key.to_pkcs8_der(),
}
}
}
#[test]
fn test_read_write_pkcs8() {
let secret = ed25519_dalek::SigningKey::generate(&mut key::safe_rng());
assert_eq!(
secret.verifying_key().as_bytes(),
ed25519_dalek::VerifyingKey::from(&secret).as_bytes()
);
let key = key::KeyPair::Ed25519(secret);
let password = b"blabla";
let ciphertext = encode_pkcs8_encrypted(password, 100, &key).unwrap();
let key = decode_pkcs8(&ciphertext, Some(password)).unwrap();
match key {
key::KeyPair::Ed25519 { .. } => println!("Ed25519"),
key::KeyPair::EC { .. } => println!("EC"),
key::KeyPair::RSA { .. } => println!("RSA"),
}
}
/// Encode into a password-protected PKCS#8-encoded private key.
pub fn encode_pkcs8_encrypted(
pass: &[u8],
rounds: u32,
key: &key::KeyPair,
) -> Result<Vec<u8>, Error> {
let pvi_bytes = encode_pkcs8(key)?;
let pvi = PrivateKeyInfo::try_from(pvi_bytes.as_slice())?;
use rand::RngCore;
let mut rng = rand::thread_rng();
let mut salt = [0; 64];
rng.fill_bytes(&mut salt);
let mut iv = [0; 16];
rng.fill_bytes(&mut iv);
let doc = pvi.encrypt_with_params(
pkcs5::pbes2::Parameters::pbkdf2_sha256_aes256cbc(rounds, &salt, &iv)
.map_err(|_| Error::InvalidParameters)?,
pass,
)?;
Ok(doc.as_bytes().to_vec())
}
/// Encode into a PKCS#8-encoded private key.
pub fn encode_pkcs8(key: &key::KeyPair) -> Result<Vec<u8>, Error> {
let v = match *key {
key::KeyPair::Ed25519(ref pair) => pair.to_pkcs8_der()?,
key::KeyPair::RSA { ref key, .. } => SecretDocument::try_from(key)?,
key::KeyPair::EC { ref key, .. } => key.to_pkcs8_der()?,
}
.as_bytes()
.to_vec();
Ok(v)
}

View File

@@ -0,0 +1,212 @@
use std::borrow::Cow;
use std::convert::TryFrom;
use aes::cipher::{BlockDecryptMut, KeyIvInit};
use aes::*;
use block_padding::Pkcs7;
use yasna::BERReaderSeq;
use super::Encryption;
use crate::{key, Error};
const PBES2: &[u64] = &[1, 2, 840, 113549, 1, 5, 13];
const ED25519: &[u64] = &[1, 3, 101, 112];
const PBKDF2: &[u64] = &[1, 2, 840, 113549, 1, 5, 12];
const AES256CBC: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 1, 42];
const HMAC_SHA256: &[u64] = &[1, 2, 840, 113549, 2, 9];
pub fn decode_pkcs8(ciphertext: &[u8], password: Option<&[u8]>) -> Result<key::KeyPair, Error> {
let secret = if let Some(pass) = password {
Cow::Owned(yasna::parse_der(ciphertext, |reader| {
reader.read_sequence(|reader| {
// Encryption parameters
let parameters = reader.next().read_sequence(|reader| {
let oid = reader.next().read_oid()?;
if oid.components().as_slice() == PBES2 {
asn1_read_pbes2(reader)
} else {
Ok(Err(Error::InvalidParameters))
}
})?;
// Ciphertext
let ciphertext = reader.next().read_bytes()?;
Ok(parameters.map(|p| p.decrypt(pass, &ciphertext)))
})
})???)
} else {
Cow::Borrowed(ciphertext)
};
yasna::parse_der(&secret, |reader| {
reader.read_sequence(|reader| {
let version = reader.next().read_u64()?;
if version == 0 {
Ok(Err(Error::CouldNotReadKey))
} else if version == 1 {
Ok(read_key_v1(reader))
} else {
Ok(Err(Error::CouldNotReadKey))
}
})
})?
}
fn read_key_v1(reader: &mut BERReaderSeq) -> Result<key::KeyPair, Error> {
let oid = reader
.next()
.read_sequence(|reader| reader.next().read_oid())?;
if oid.components().as_slice() == ED25519 {
use ed25519_dalek::SigningKey;
let secret = {
let s = yasna::parse_der(&reader.next().read_bytes()?, |reader| reader.read_bytes())?;
s.get(..ed25519_dalek::SECRET_KEY_LENGTH)
.ok_or(Error::KeyIsCorrupt)
.and_then(|s| SigningKey::try_from(s).map_err(|_| Error::CouldNotReadKey))?
};
// Consume the public key
reader
.next()
.read_tagged(yasna::Tag::context(1), |reader| reader.read_bitvec())?;
Ok(key::KeyPair::Ed25519(secret))
} else {
Err(Error::CouldNotReadKey)
}
}
#[derive(Debug)]
enum Key {
K128([u8; 16]),
K256([u8; 32]),
}
impl std::ops::Deref for Key {
type Target = [u8];
fn deref(&self) -> &[u8] {
match *self {
Key::K128(ref k) => k,
Key::K256(ref k) => k,
}
}
}
impl std::ops::DerefMut for Key {
fn deref_mut(&mut self) -> &mut [u8] {
match *self {
Key::K128(ref mut k) => k,
Key::K256(ref mut k) => k,
}
}
}
enum Algorithms {
Pbes2(KeyDerivation, Encryption),
}
impl Algorithms {
fn decrypt(&self, password: &[u8], cipher: &[u8]) -> Result<Vec<u8>, Error> {
match *self {
Algorithms::Pbes2(ref der, ref enc) => {
let mut key = enc.key();
der.derive(password, &mut key)?;
let out = enc.decrypt(&key, cipher)?;
Ok(out)
}
}
}
}
impl Encryption {
fn key(&self) -> Key {
match *self {
Encryption::Aes128Cbc(_) => Key::K128([0; 16]),
Encryption::Aes256Cbc(_) => Key::K256([0; 32]),
}
}
fn decrypt(&self, key: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, Error> {
match *self {
Encryption::Aes128Cbc(ref iv) => {
#[allow(clippy::unwrap_used)] // parameters are static
let c = cbc::Decryptor::<Aes128>::new_from_slices(key, iv).unwrap();
let mut dec = ciphertext.to_vec();
Ok(c.decrypt_padded_mut::<Pkcs7>(&mut dec)?.into())
}
Encryption::Aes256Cbc(ref iv) => {
#[allow(clippy::unwrap_used)] // parameters are static
let c = cbc::Decryptor::<Aes256>::new_from_slices(key, iv).unwrap();
let mut dec = ciphertext.to_vec();
Ok(c.decrypt_padded_mut::<Pkcs7>(&mut dec)?.into())
}
}
}
}
enum KeyDerivation {
Pbkdf2 { salt: Vec<u8>, rounds: u64 },
}
impl KeyDerivation {
fn derive(&self, password: &[u8], key: &mut [u8]) -> Result<(), Error> {
match *self {
KeyDerivation::Pbkdf2 { ref salt, rounds } => {
pbkdf2::pbkdf2::<hmac::Hmac<sha2::Sha256>>(password, salt, rounds as u32, key)
.map_err(|_| Error::InvalidParameters)
// pbkdf2_hmac(password, salt, rounds as usize, digest, key)?
}
}
}
}
fn asn1_read_pbes2(
reader: &mut yasna::BERReaderSeq,
) -> Result<Result<Algorithms, Error>, yasna::ASN1Error> {
reader.next().read_sequence(|reader| {
// PBES2 has two components.
// 1. Key generation algorithm
let keygen = reader.next().read_sequence(|reader| {
let oid = reader.next().read_oid()?;
if oid.components().as_slice() == PBKDF2 {
asn1_read_pbkdf2(reader)
} else {
Ok(Err(Error::InvalidParameters))
}
})?;
// 2. Encryption algorithm.
let algorithm = reader.next().read_sequence(|reader| {
let oid = reader.next().read_oid()?;
if oid.components().as_slice() == AES256CBC {
asn1_read_aes256cbc(reader)
} else {
Ok(Err(Error::InvalidParameters))
}
})?;
Ok(keygen.and_then(|keygen| algorithm.map(|algo| Algorithms::Pbes2(keygen, algo))))
})
}
fn asn1_read_pbkdf2(
reader: &mut yasna::BERReaderSeq,
) -> Result<Result<KeyDerivation, Error>, yasna::ASN1Error> {
reader.next().read_sequence(|reader| {
let salt = reader.next().read_bytes()?;
let rounds = reader.next().read_u64()?;
let digest = reader.next().read_sequence(|reader| {
let oid = reader.next().read_oid()?;
if oid.components().as_slice() == HMAC_SHA256 {
reader.next().read_null()?;
Ok(Ok(()))
} else {
Ok(Err(Error::InvalidParameters))
}
})?;
Ok(digest.map(|()| KeyDerivation::Pbkdf2 { salt, rounds }))
})
}
fn asn1_read_aes256cbc(
reader: &mut yasna::BERReaderSeq,
) -> Result<Result<Encryption, Error>, yasna::ASN1Error> {
let iv = reader.next().read_bytes()?;
let mut i = [0; 16];
i.clone_from_slice(&iv);
Ok(Ok(Encryption::Aes256Cbc(i)))
}

482
vendor/russh-keys/src/key.rs vendored Normal file
View File

@@ -0,0 +1,482 @@
// Copyright 2016 Pierre-Étienne Meunier
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
use std::borrow::Cow;
use std::convert::{TryFrom, TryInto};
pub use backend::{RsaPrivate, RsaPublic};
use ed25519_dalek::{Signer, Verifier};
use rand_core::OsRng;
use russh_cryptovec::CryptoVec;
use serde::{Deserialize, Serialize};
use crate::encoding::{Encoding, Reader};
pub use crate::signature::*;
use crate::{backend, ec, protocol, Error};
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
/// Name of a public key algorithm.
pub struct Name(pub &'static str);
impl AsRef<str> for Name {
fn as_ref(&self) -> &str {
self.0
}
}
/// 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-nistp384 algorithm for SSH.
pub const ECDSA_SHA2_NISTP384: Name = Name("ecdsa-sha2-nistp384");
/// 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.
pub const RSA_SHA2_512: Name = Name("rsa-sha2-512");
/// The name of the ssh-sha2-256 algorithm for SSH.
pub const RSA_SHA2_256: Name = Name("rsa-sha2-256");
pub const NONE: Name = Name("none");
pub const SSH_RSA: Name = Name("ssh-rsa");
pub static ALL_KEY_TYPES: &[&Name] = &[
&NONE,
&ED25519,
&SSH_RSA,
&RSA_SHA2_256,
&RSA_SHA2_512,
&ECDSA_SHA2_NISTP256,
&ECDSA_SHA2_NISTP384,
&ECDSA_SHA2_NISTP521,
];
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 | ECDSA_SHA2_NISTP384 | ECDSA_SHA2_NISTP521 => "id_ecdsa",
ED25519 => "id_ed25519",
RSA_SHA2_512 => "id_rsa",
RSA_SHA2_256 => "id_rsa",
_ => unreachable!(),
}
}
}
impl TryFrom<&str> for Name {
type Error = ();
fn try_from(s: &str) -> Result<Name, ()> {
ALL_KEY_TYPES
.iter()
.find(|x| x.0 == s)
.map(|x| **x)
.ok_or(())
}
}
#[doc(hidden)]
pub trait Verify {
fn verify_client_auth(&self, buffer: &[u8], sig: &[u8]) -> bool;
fn verify_server_auth(&self, buffer: &[u8], sig: &[u8]) -> bool;
}
/// The hash function used for signing with RSA keys.
#[derive(Eq, PartialEq, Clone, Copy, Debug, Hash, Serialize, Deserialize)]
#[allow(non_camel_case_types)]
pub enum SignatureHash {
/// SHA2, 256 bits.
SHA2_256,
/// SHA2, 512 bits.
SHA2_512,
/// SHA1
SHA1,
}
impl SignatureHash {
pub fn name(&self) -> Name {
match *self {
SignatureHash::SHA2_256 => RSA_SHA2_256,
SignatureHash::SHA2_512 => RSA_SHA2_512,
SignatureHash::SHA1 => SSH_RSA,
}
}
pub fn from_rsa_hostkey_algo(algo: &[u8]) -> Option<Self> {
match algo {
b"rsa-sha2-256" => Some(Self::SHA2_256),
b"rsa-sha2-512" => Some(Self::SHA2_512),
b"ssh-rsa" => Some(Self::SHA1),
_ => None,
}
}
}
/// Public key
#[derive(Eq, Debug, Clone)]
pub enum PublicKey {
#[doc(hidden)]
Ed25519(ed25519_dalek::VerifyingKey),
#[doc(hidden)]
RSA {
key: backend::RsaPublic,
hash: SignatureHash,
},
#[doc(hidden)]
EC { key: ec::PublicKey },
}
impl PartialEq for PublicKey {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::RSA { key: a, .. }, Self::RSA { key: b, .. }) => a == b,
(Self::Ed25519(a), Self::Ed25519(b)) => a == b,
(Self::EC { key: a }, Self::EC { key: b }) => a == b,
_ => false,
}
}
}
impl PublicKey {
/// Parse a public key in SSH format.
pub fn parse(algo: &[u8], pubkey: &[u8]) -> Result<Self, Error> {
use ssh_encoding::Decode;
let key_data = &ssh_key::public::KeyData::decode(&mut pubkey.reader(0))?;
let key_algo = key_data.algorithm();
let key_algo = key_algo.as_str().as_bytes();
if key_algo == b"ssh-rsa" {
if algo != SSH_RSA.as_ref().as_bytes()
&& algo != RSA_SHA2_256.as_ref().as_bytes()
&& algo != RSA_SHA2_512.as_ref().as_bytes()
{
return Err(Error::KeyIsCorrupt);
}
} else if key_algo != algo {
return Err(Error::KeyIsCorrupt);
}
Self::try_from(key_data)
}
pub fn new_rsa_with_hash(
pk: &protocol::RsaPublicKey<'_>,
hash: SignatureHash,
) -> Result<Self, Error> {
Ok(PublicKey::RSA {
key: RsaPublic::try_from(pk)?,
hash,
})
}
/// Algorithm name for that key.
pub fn name(&self) -> &'static str {
match *self {
PublicKey::Ed25519(_) => ED25519.0,
PublicKey::RSA { ref hash, .. } => hash.name().0,
PublicKey::EC { ref key } => key.algorithm(),
}
}
/// Verify a signature.
pub fn verify_detached(&self, buffer: &[u8], sig: &[u8]) -> bool {
match self {
PublicKey::Ed25519(ref public) => {
let Ok(sig) = ed25519_dalek::ed25519::SignatureBytes::try_from(sig) else {
return false;
};
let sig = ed25519_dalek::Signature::from_bytes(&sig);
public.verify(buffer, &sig).is_ok()
}
PublicKey::RSA { ref key, ref hash } => key.verify_detached(hash, buffer, sig),
PublicKey::EC { ref key, .. } => ec_verify(key, buffer, sig).is_ok(),
}
}
/// Compute the key fingerprint, hashed with sha2-256.
pub fn fingerprint(&self) -> String {
use super::PublicKeyBase64;
let key = self.public_key_bytes();
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(&key[..]);
data_encoding::BASE64_NOPAD.encode(&hasher.finalize())
}
pub fn set_algorithm(&mut self, algorithm: SignatureHash) {
if let PublicKey::RSA { ref mut hash, .. } = self {
*hash = algorithm;
}
}
}
impl Verify for PublicKey {
fn verify_client_auth(&self, buffer: &[u8], sig: &[u8]) -> bool {
self.verify_detached(buffer, sig)
}
fn verify_server_auth(&self, buffer: &[u8], sig: &[u8]) -> bool {
self.verify_detached(buffer, sig)
}
}
/// Public key exchange algorithms.
#[allow(clippy::large_enum_variant)]
pub enum KeyPair {
Ed25519(ed25519_dalek::SigningKey),
RSA {
key: backend::RsaPrivate,
hash: SignatureHash,
},
EC {
key: ec::PrivateKey,
},
}
impl Clone for KeyPair {
fn clone(&self) -> Self {
match self {
#[allow(clippy::expect_used)]
Self::Ed25519(kp) => {
Self::Ed25519(ed25519_dalek::SigningKey::from_bytes(&kp.to_bytes()))
}
Self::RSA { key, hash } => Self::RSA {
key: key.clone(),
hash: *hash,
},
Self::EC { key } => Self::EC { key: key.clone() },
}
}
}
impl std::fmt::Debug for KeyPair {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
KeyPair::Ed25519(ref key) => write!(
f,
"Ed25519 {{ public: {:?}, secret: (hidden) }}",
key.verifying_key().as_bytes()
),
KeyPair::RSA { .. } => write!(f, "RSA {{ (hidden) }}"),
KeyPair::EC { .. } => write!(f, "EC {{ (hidden) }}"),
}
}
}
impl<'b> crate::encoding::Bytes for &'b KeyPair {
fn bytes(&self) -> &[u8] {
self.name().as_bytes()
}
}
impl KeyPair {
pub fn new_rsa_with_hash(
sk: &protocol::RsaPrivateKey<'_>,
extra: Option<&RsaCrtExtra<'_>>,
hash: SignatureHash,
) -> Result<KeyPair, Error> {
Ok(KeyPair::RSA {
key: RsaPrivate::new(sk, extra)?,
hash,
})
}
/// Copy the public key of this algorithm.
pub fn clone_public_key(&self) -> Result<PublicKey, Error> {
Ok(match self {
KeyPair::Ed25519(ref key) => PublicKey::Ed25519(key.verifying_key()),
KeyPair::RSA { ref key, ref hash } => PublicKey::RSA {
key: key.try_into()?,
hash: *hash,
},
KeyPair::EC { ref key } => PublicKey::EC {
key: key.to_public_key(),
},
})
}
/// Name of this key algorithm.
pub fn name(&self) -> &'static str {
match *self {
KeyPair::Ed25519(_) => ED25519.0,
KeyPair::RSA { ref hash, .. } => hash.name().0,
KeyPair::EC { ref key } => key.algorithm(),
}
}
/// Generate a ED25519 key pair.
pub fn generate_ed25519() -> Self {
let keypair = ed25519_dalek::SigningKey::generate(&mut OsRng {});
assert_eq!(
keypair.verifying_key().as_bytes(),
ed25519_dalek::VerifyingKey::from(&keypair).as_bytes()
);
KeyPair::Ed25519(keypair)
}
/// Generate a RSA key pair.
pub fn generate_rsa(bits: usize, hash: SignatureHash) -> Option<Self> {
let key = RsaPrivate::generate(bits).ok()?;
Some(KeyPair::RSA { key, hash })
}
/// 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::RSA { ref key, ref hash } => Ok(Signature::RSA {
bytes: key.sign(hash, to_sign)?,
hash: *hash,
}),
KeyPair::EC { ref key } => Ok(Signature::ECDSA {
algorithm: key.algorithm(),
signature: ec_signature(key, to_sign)?,
}),
}
}
#[doc(hidden)]
/// This is used by the server to sign the initial DH kex
/// message. Note: we are not signing the same kind of thing as in
/// the function below, `add_self_signature`.
pub fn add_signature<H: AsRef<[u8]>>(
&self,
buffer: &mut CryptoVec,
to_sign: H,
) -> Result<(), Error> {
match self {
KeyPair::Ed25519(ref secret) => {
let signature = secret.sign(to_sign.as_ref());
buffer.push_u32_be((ED25519.0.len() + signature.to_bytes().len() + 8) as u32);
buffer.extend_ssh_string(ED25519.0.as_bytes());
buffer.extend_ssh_string(signature.to_bytes().as_slice());
}
KeyPair::RSA { ref key, ref hash } => {
// https://tools.ietf.org/html/draft-rsa-dsa-sha2-256-02#section-2.2
let signature = key.sign(hash, to_sign.as_ref())?;
let name = hash.name();
buffer.push_u32_be((name.0.len() + signature.len() + 8) as u32);
buffer.extend_ssh_string(name.0.as_bytes());
buffer.extend_ssh_string(&signature);
}
KeyPair::EC { ref key } => {
let algorithm = key.algorithm().as_bytes();
let signature = ec_signature(key, to_sign.as_ref())?;
buffer.push_u32_be((algorithm.len() + signature.len() + 8) as u32);
buffer.extend_ssh_string(algorithm);
buffer.extend_ssh_string(&signature);
}
}
Ok(())
}
#[doc(hidden)]
/// This is used by the client for authentication. Note: we are
/// not signing the same kind of thing as in the above function,
/// `add_signature`.
pub fn add_self_signature(&self, buffer: &mut CryptoVec) -> Result<(), Error> {
match self {
KeyPair::Ed25519(ref secret) => {
let signature = secret.sign(buffer);
buffer.push_u32_be((ED25519.0.len() + signature.to_bytes().len() + 8) as u32);
buffer.extend_ssh_string(ED25519.0.as_bytes());
buffer.extend_ssh_string(signature.to_bytes().as_slice());
}
KeyPair::RSA { ref key, ref hash } => {
// https://tools.ietf.org/html/draft-rsa-dsa-sha2-256-02#section-2.2
let signature = key.sign(hash, buffer)?;
let name = hash.name();
buffer.push_u32_be((name.0.len() + signature.len() + 8) as u32);
buffer.extend_ssh_string(name.0.as_bytes());
buffer.extend_ssh_string(&signature);
}
KeyPair::EC { ref key } => {
let signature = ec_signature(key, buffer)?;
let algorithm = key.algorithm().as_bytes();
buffer.push_u32_be((algorithm.len() + signature.len() + 8) as u32);
buffer.extend_ssh_string(algorithm);
buffer.extend_ssh_string(&signature);
}
}
Ok(())
}
/// Create a copy of an RSA key with a specified hash algorithm.
pub fn with_signature_hash(&self, hash: SignatureHash) -> Option<Self> {
match self {
KeyPair::Ed25519(_) => None,
KeyPair::RSA { key, .. } => Some(KeyPair::RSA {
key: key.clone(),
hash,
}),
KeyPair::EC { .. } => None,
}
}
}
/// Extra CRT parameters for RSA private key.
pub struct RsaCrtExtra<'a> {
/// `d mod (p-1)`.
pub dp: Cow<'a, [u8]>,
/// `d mod (q-1)`.
pub dq: Cow<'a, [u8]>,
}
impl Drop for RsaCrtExtra<'_> {
fn drop(&mut self) {
zeroize_cow(&mut self.dp);
zeroize_cow(&mut self.dq);
}
}
fn ec_signature(key: &ec::PrivateKey, b: &[u8]) -> Result<Vec<u8>, Error> {
let (r, s) = key.try_sign(b)?;
let mut buf = Vec::new();
buf.extend_ssh_mpint(&r);
buf.extend_ssh_mpint(&s);
Ok(buf)
}
fn ec_verify(key: &ec::PublicKey, b: &[u8], sig: &[u8]) -> Result<(), Error> {
let mut reader = sig.reader(0);
key.verify(b, reader.read_mpint()?, reader.read_mpint()?)
}
/// Parse a public key from a byte slice.
pub fn parse_public_key(p: &[u8], prefer_hash: Option<SignatureHash>) -> Result<PublicKey, Error> {
use ssh_encoding::Decode;
let mut key = PublicKey::try_from(&ssh_key::public::KeyData::decode(&mut p.reader(0))?)?;
key.set_algorithm(prefer_hash.unwrap_or(SignatureHash::SHA2_256));
Ok(key)
}
/// Obtain a cryptographic-safe random number generator.
pub fn safe_rng() -> impl rand::CryptoRng + rand::RngCore {
rand::thread_rng()
}
/// Zeroize `Cow` if value is owned.
pub(crate) fn zeroize_cow<T>(v: &mut Cow<T>)
where
T: ToOwned + ?Sized,
<T as ToOwned>::Owned: zeroize::Zeroize,
{
use zeroize::Zeroize;
match v {
Cow::Owned(v) => v.zeroize(),
Cow::Borrowed(_) => (),
}
}

246
vendor/russh-keys/src/known_hosts.rs vendored Normal file
View File

@@ -0,0 +1,246 @@
use std::borrow::Cow;
use std::fs::{File, OpenOptions};
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
use data_encoding::BASE64_MIME;
use hmac::{Hmac, Mac};
use log::debug;
use sha1::Sha1;
use crate::{key, Error, PublicKeyBase64};
/// Check whether the host is known, from its standard location.
pub fn check_known_hosts(host: &str, port: u16, pubkey: &key::PublicKey) -> Result<bool, Error> {
check_known_hosts_path(host, port, pubkey, known_hosts_path()?)
}
/// Check that a server key matches the one recorded in file `path`.
pub fn check_known_hosts_path<P: AsRef<Path>>(
host: &str,
port: u16,
pubkey: &key::PublicKey,
path: P,
) -> Result<bool, Error> {
let check = known_host_keys_path(host, port, path)?
.into_iter()
.map(
|(line, recorded)| match (pubkey.name() == recorded.name(), *pubkey == recorded) {
(true, true) => Ok(true),
(true, false) => Err(Error::KeyChanged { line }),
_ => Ok(false),
},
)
// If any Err was returned, we stop here
.collect::<Result<Vec<bool>, Error>>()?
.into_iter()
// Now we check the results for a match
.any(|x| x);
Ok(check)
}
#[cfg(target_os = "windows")]
fn known_hosts_path() -> Result<PathBuf, Error> {
if let Some(home_dir) = home::home_dir() {
Ok(home_dir.join("ssh").join("known_hosts"))
} else {
Err(Error::NoHomeDir)
}
}
#[cfg(not(target_os = "windows"))]
fn known_hosts_path() -> Result<PathBuf, Error> {
if let Some(home_dir) = home::home_dir() {
Ok(home_dir.join(".ssh").join("known_hosts"))
} else {
Err(Error::NoHomeDir)
}
}
/// Get the server key that matches the one recorded in the user's known_hosts file.
pub fn known_host_keys(host: &str, port: u16) -> Result<Vec<(usize, key::PublicKey)>, Error> {
known_host_keys_path(host, port, known_hosts_path()?)
}
/// Get the server key that matches the one recorded in `path`.
pub fn known_host_keys_path<P: AsRef<Path>>(
host: &str,
port: u16,
path: P,
) -> Result<Vec<(usize, key::PublicKey)>, Error> {
use crate::parse_public_key_base64;
let mut f = if let Ok(f) = File::open(path) {
BufReader::new(f)
} else {
return Ok(vec![]);
};
let mut buffer = String::new();
let host_port = if port == 22 {
Cow::Borrowed(host)
} else {
Cow::Owned(format!("[{}]:{}", host, port))
};
debug!("host_port = {:?}", host_port);
let mut line = 1;
let mut matches = vec![];
while f.read_line(&mut buffer)? > 0 {
{
if buffer.as_bytes().first() == Some(&b'#') {
buffer.clear();
continue;
}
debug!("line = {:?}", buffer);
let mut s = buffer.split(' ');
let hosts = s.next();
let _ = s.next();
let key = s.next();
if let (Some(h), Some(k)) = (hosts, key) {
debug!("{:?} {:?}", h, k);
if match_hostname(&host_port, h) {
matches.push((line, parse_public_key_base64(k)?));
}
}
}
buffer.clear();
line += 1;
}
Ok(matches)
}
fn match_hostname(host: &str, pattern: &str) -> bool {
for entry in pattern.split(',') {
if entry.starts_with("|1|") {
let mut parts = entry.split('|').skip(2);
let Some(Ok(salt)) = parts.next().map(|p| BASE64_MIME.decode(p.as_bytes())) else {
continue;
};
let Some(Ok(hash)) = parts.next().map(|p| BASE64_MIME.decode(p.as_bytes())) else {
continue;
};
if let Ok(hmac) = Hmac::<Sha1>::new_from_slice(&salt) {
if hmac.chain_update(host).verify_slice(&hash).is_ok() {
return true;
}
}
} else if host == entry {
return true;
}
}
false
}
/// Record a host's public key into the user's known_hosts file.
pub fn learn_known_hosts(host: &str, port: u16, pubkey: &key::PublicKey) -> Result<(), Error> {
learn_known_hosts_path(host, port, pubkey, known_hosts_path()?)
}
/// Record a host's public key into a nonstandard location.
pub fn learn_known_hosts_path<P: AsRef<Path>>(
host: &str,
port: u16,
pubkey: &key::PublicKey,
path: P,
) -> Result<(), Error> {
if let Some(parent) = path.as_ref().parent() {
std::fs::create_dir_all(parent)?
}
let mut file = OpenOptions::new()
.read(true)
.append(true)
.create(true)
.open(path)?;
// Test whether the known_hosts file ends with a \n
let mut buf = [0; 1];
let mut ends_in_newline = false;
if file.seek(SeekFrom::End(-1)).is_ok() {
file.read_exact(&mut buf)?;
ends_in_newline = buf[0] == b'\n';
}
// Write the key.
file.seek(SeekFrom::End(0))?;
let mut file = std::io::BufWriter::new(file);
if !ends_in_newline {
file.write_all(b"\n")?;
}
if port != 22 {
write!(file, "[{}]:{} ", host, port)?
} else {
write!(file, "{} ", host)?
}
write_public_key_base64(&mut file, pubkey)?;
file.write_all(b"\n")?;
Ok(())
}
/// Write a public key onto the provided `Write`, encoded in base-64.
pub fn write_public_key_base64<W: Write>(
mut w: W,
publickey: &key::PublicKey,
) -> Result<(), Error> {
let pk = publickey.public_key_base64();
writeln!(w, "{} {}", publickey.name(), pk)?;
Ok(())
}
#[cfg(test)]
mod test {
use std::fs::File;
use super::*;
use crate::parse_public_key_base64;
#[test]
fn test_check_known_hosts() {
env_logger::try_init().unwrap_or(());
let dir = tempdir::TempDir::new("russh").unwrap();
let path = dir.path().join("known_hosts");
{
let mut f = File::create(&path).unwrap();
f.write_all(b"[localhost]:13265 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJdD7y3aLq454yWBdwLWbieU1ebz9/cu7/QEXn9OIeZJ\n").unwrap();
f.write_all(b"#pijul.org,37.120.161.53 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA6rWI3G2sz07DnfFlrouTcysQlj2P+jpNSOEWD9OJ3X\n").unwrap();
f.write_all(b"pijul.org,37.120.161.53 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA6rWI3G1sz07DnfFlrouTcysQlj2P+jpNSOEWD9OJ3X\n").unwrap();
f.write_all(b"|1|O33ESRMWPVkMYIwJ1Uw+n877jTo=|nuuC5vEqXlEZ/8BXQR7m619W6Ak= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILIG2T/B0l0gaqj3puu510tu9N1OkQ4znY3LYuEm5zCF\n").unwrap();
}
// Valid key, non-standard port.
let host = "localhost";
let port = 13265;
let hostkey = parse_public_key_base64(
"AAAAC3NzaC1lZDI1NTE5AAAAIJdD7y3aLq454yWBdwLWbieU1ebz9/cu7/QEXn9OIeZJ",
)
.unwrap();
assert!(check_known_hosts_path(host, port, &hostkey, &path).unwrap());
// Valid key, hashed.
let host = "example.com";
let port = 22;
let hostkey = parse_public_key_base64(
"AAAAC3NzaC1lZDI1NTE5AAAAILIG2T/B0l0gaqj3puu510tu9N1OkQ4znY3LYuEm5zCF",
)
.unwrap();
assert!(check_known_hosts_path(host, port, &hostkey, &path).unwrap());
// Valid key, several hosts, port 22
let host = "pijul.org";
let port = 22;
let hostkey = parse_public_key_base64(
"AAAAC3NzaC1lZDI1NTE5AAAAIA6rWI3G1sz07DnfFlrouTcysQlj2P+jpNSOEWD9OJ3X",
)
.unwrap();
assert!(check_known_hosts_path(host, port, &hostkey, &path).unwrap());
// Now with the key in a comment above, check that it's not recognized
let host = "pijul.org";
let port = 22;
let hostkey = parse_public_key_base64(
"AAAAC3NzaC1lZDI1NTE5AAAAIA6rWI3G2sz07DnfFlrouTcysQlj2P+jpNSOEWD9OJ3X",
)
.unwrap();
assert!(check_known_hosts_path(host, port, &hostkey, &path).is_err());
}
}

1173
vendor/russh-keys/src/lib.rs vendored Normal file

File diff suppressed because it is too large Load Diff

87
vendor/russh-keys/src/protocol.rs vendored Normal file
View File

@@ -0,0 +1,87 @@
use std::borrow::Cow;
use crate::encoding::{Encoding, Position, SshRead, SshWrite};
use crate::key::zeroize_cow;
type Result<T> = std::result::Result<T, crate::Error>;
/// SSH RSA public key.
pub struct RsaPublicKey<'a> {
/// `e`: RSA public exponent.
pub public_exponent: Cow<'a, [u8]>,
/// `n`: RSA modulus.
pub modulus: Cow<'a, [u8]>,
}
impl<'a> SshRead<'a> for RsaPublicKey<'a> {
fn read_ssh(pos: &mut Position<'a>) -> Result<Self> {
Ok(Self {
public_exponent: Cow::Borrowed(pos.read_mpint()?),
modulus: Cow::Borrowed(pos.read_mpint()?),
})
}
}
impl SshWrite for RsaPublicKey<'_> {
fn write_ssh<E: Encoding + ?Sized>(&self, encoder: &mut E) {
encoder.extend_ssh_mpint(&self.public_exponent);
encoder.extend_ssh_mpint(&self.modulus);
}
}
/// SSH RSA private key.
pub struct RsaPrivateKey<'a> {
/// RSA public key.
pub public_key: RsaPublicKey<'a>,
/// `d`: RSA private exponent.
pub private_exponent: Cow<'a, [u8]>,
/// CRT coefficient: `(inverse of q) mod p`.
pub coefficient: Cow<'a, [u8]>,
/// `p`: first prime factor of `n`.
pub prime1: Cow<'a, [u8]>,
/// `q`: Second prime factor of `n`.
pub prime2: Cow<'a, [u8]>,
/// Comment.
pub comment: Cow<'a, [u8]>,
}
impl<'a> SshRead<'a> for RsaPrivateKey<'a> {
fn read_ssh(pos: &mut Position<'a>) -> Result<Self> {
Ok(Self {
// Note the field order.
public_key: RsaPublicKey {
modulus: Cow::Borrowed(pos.read_mpint()?),
public_exponent: Cow::Borrowed(pos.read_mpint()?),
},
private_exponent: Cow::Borrowed(pos.read_mpint()?),
coefficient: Cow::Borrowed(pos.read_mpint()?),
prime1: Cow::Borrowed(pos.read_mpint()?),
prime2: Cow::Borrowed(pos.read_mpint()?),
comment: Cow::Borrowed(pos.read_string()?),
})
}
}
impl SshWrite for RsaPrivateKey<'_> {
fn write_ssh<E: Encoding + ?Sized>(&self, encoder: &mut E) {
// Note the field order.
encoder.extend_ssh_mpint(&self.public_key.modulus);
encoder.extend_ssh_mpint(&self.public_key.public_exponent);
encoder.extend_ssh_mpint(&self.private_exponent);
encoder.extend_ssh_mpint(&self.coefficient);
encoder.extend_ssh_mpint(&self.prime1);
encoder.extend_ssh_mpint(&self.prime2);
encoder.extend_ssh_string(&self.comment);
}
}
impl Drop for RsaPrivateKey<'_> {
fn drop(&mut self) {
// Private parts only.
zeroize_cow(&mut self.private_exponent);
zeroize_cow(&mut self.coefficient);
zeroize_cow(&mut self.prime1);
zeroize_cow(&mut self.prime2);
zeroize_cow(&mut self.comment);
}
}

190
vendor/russh-keys/src/signature.rs vendored Normal file
View File

@@ -0,0 +1,190 @@
use std::fmt;
use byteorder::{BigEndian, WriteBytesExt};
use serde;
use serde::de::{SeqAccess, Visitor};
use serde::ser::SerializeTuple;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::key::SignatureHash;
use crate::Error;
pub struct SignatureBytes(pub [u8; 64]);
/// The type of a signature, depending on the algorithm used.
#[derive(Serialize, Deserialize, Clone)]
pub enum Signature {
/// An Ed25519 signature
Ed25519(SignatureBytes),
/// An RSA signature
RSA { hash: SignatureHash, bytes: Vec<u8> },
/// An ECDSA signature
ECDSA {
/// Algorithm name defined in RFC 5656 section 3.1.2, in the form of
/// `"ecdsa-sha2-[identifier]"`.
algorithm: &'static str,
/// Signature blob defined in RFC 5656 section 3.1.2.
signature: Vec<u8>,
},
}
impl Signature {
pub fn to_base64(&self) -> String {
use crate::encoding::Encoding;
let mut bytes_ = Vec::new();
match self {
Signature::Ed25519(ref bytes) => {
let t = b"ssh-ed25519";
#[allow(clippy::unwrap_used)] // Vec<>.write_all can't fail
bytes_
.write_u32::<BigEndian>((t.len() + bytes.0.len() + 8) as u32)
.unwrap();
bytes_.extend_ssh_string(t);
bytes_.extend_ssh_string(&bytes.0[..]);
}
Signature::RSA {
ref hash,
ref bytes,
} => {
let t = match hash {
SignatureHash::SHA2_256 => &b"rsa-sha2-256"[..],
SignatureHash::SHA2_512 => &b"rsa-sha2-512"[..],
SignatureHash::SHA1 => &b"ssh-rsa"[..],
};
#[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::ECDSA {
algorithm,
signature,
} => {
let algorithm = algorithm.as_bytes();
#[allow(clippy::unwrap_used)] // Vec<>.write_all can't fail
bytes_
.write_u32::<BigEndian>((algorithm.len() + signature.len() + 8) as u32)
.unwrap();
bytes_.extend_ssh_string(algorithm);
bytes_.extend_ssh_string(signature);
}
}
data_encoding::BASE64_NOPAD.encode(&bytes_[..])
}
pub fn from_base64(s: &[u8]) -> Result<Self, Error> {
let bytes_ = data_encoding::BASE64_NOPAD.decode(s)?;
use crate::encoding::Reader;
let mut r = bytes_.reader(0);
let sig = r.read_string()?;
let mut r = sig.reader(0);
let typ = r.read_string()?;
let bytes = r.read_string()?;
match typ {
b"ssh-ed25519" => {
let mut bytes_ = [0; 64];
bytes_.clone_from_slice(bytes);
Ok(Signature::Ed25519(SignatureBytes(bytes_)))
}
b"rsa-sha2-256" => Ok(Signature::RSA {
hash: SignatureHash::SHA2_256,
bytes: bytes.to_vec(),
}),
b"rsa-sha2-512" => Ok(Signature::RSA {
hash: SignatureHash::SHA2_512,
bytes: bytes.to_vec(),
}),
b"ssh-rsa" => Ok(Signature::RSA {
hash: SignatureHash::SHA1,
bytes: bytes.to_vec(),
}),
crate::KEYTYPE_ECDSA_SHA2_NISTP256 => Ok(Signature::ECDSA {
algorithm: crate::ECDSA_SHA2_NISTP256,
signature: bytes.to_vec(),
}),
crate::KEYTYPE_ECDSA_SHA2_NISTP384 => Ok(Signature::ECDSA {
algorithm: crate::ECDSA_SHA2_NISTP384,
signature: bytes.to_vec(),
}),
crate::KEYTYPE_ECDSA_SHA2_NISTP521 => Ok(Signature::ECDSA {
algorithm: crate::ECDSA_SHA2_NISTP521,
signature: bytes.to_vec(),
}),
_ => Err(Error::UnknownSignatureType {
sig_type: std::str::from_utf8(typ).unwrap_or("").to_string(),
}),
}
}
}
impl AsRef<[u8]> for Signature {
fn as_ref(&self) -> &[u8] {
match *self {
Signature::Ed25519(ref signature) => &signature.0,
Signature::RSA { ref bytes, .. } => &bytes[..],
Signature::ECDSA { ref signature, .. } => &signature[..],
}
}
}
impl AsRef<[u8]> for SignatureBytes {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl<'de> Deserialize<'de> for SignatureBytes {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct Vis;
impl<'de> Visitor<'de> for Vis {
type Value = SignatureBytes;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("64 bytes")
}
fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let mut result = [0; 64];
for x in result.iter_mut() {
if let Some(y) = seq.next_element()? {
*x = y
} else {
return Err(serde::de::Error::invalid_length(64, &self));
}
}
Ok(SignatureBytes(result))
}
}
deserializer.deserialize_tuple(64, Vis)
}
}
impl Serialize for SignatureBytes {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut tup = serializer.serialize_tuple(64)?;
for byte in self.0.iter() {
tup.serialize_element(byte)?;
}
tup.end()
}
}
impl fmt::Debug for SignatureBytes {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{:?}", &self.0[..])
}
}
impl Clone for SignatureBytes {
fn clone(&self) -> Self {
let mut result = SignatureBytes([0; 64]);
result.0.clone_from_slice(&self.0);
result
}
}

Binary file not shown.

File diff suppressed because one or more lines are too long