ssh: remove unused traits, simplify code

Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
Alexis Sellier 2022-10-13 22:33:36 +02:00
parent c294f2ab5c
commit 0482f0ce04
No known key found for this signature in database
5 changed files with 88 additions and 146 deletions

View File

@ -11,9 +11,8 @@ use zeroize::Zeroize as _;
use crate::agent::msg; use crate::agent::msg;
use crate::agent::Constraint; use crate::agent::Constraint;
use crate::encoding; use crate::encoding::{self, Encodable};
use crate::encoding::{Buffer, Encoding, Reader}; use crate::encoding::{Buffer, Encoding, Reader};
use crate::key::{Private, Public};
/// An ed25519 Signature. /// An ed25519 Signature.
pub type Signature = [u8; 64]; pub type Signature = [u8; 64];
@ -87,7 +86,7 @@ impl<S: ClientStream> AgentClient<S> {
/// to apply when using the key to sign. /// to apply when using the key to sign.
pub fn add_identity<K>(&mut self, key: &K, constraints: &[Constraint]) -> Result<(), Error> pub fn add_identity<K>(&mut self, key: &K, constraints: &[Constraint]) -> Result<(), Error>
where where
K: Private, K: Encodable,
K::Error: std::error::Error + Send + Sync + 'static, K::Error: std::error::Error + Send + Sync + 'static,
{ {
self.buf.zeroize(); self.buf.zeroize();
@ -98,8 +97,7 @@ impl<S: ClientStream> AgentClient<S> {
} else { } else {
self.buf.push(msg::ADD_ID_CONSTRAINED) self.buf.push(msg::ADD_ID_CONSTRAINED)
} }
key.write(&mut self.buf) key.write(&mut self.buf);
.map_err(|err| Error::Private(Box::new(err)))?;
if !constraints.is_empty() { if !constraints.is_empty() {
for cons in constraints { for cons in constraints {
@ -203,7 +201,7 @@ impl<S: ClientStream> AgentClient<S> {
/// keys. /// keys.
pub fn request_identities<K>(&mut self) -> Result<Vec<K>, Error> pub fn request_identities<K>(&mut self) -> Result<Vec<K>, Error>
where where
K: Public, K: Encodable,
K::Error: std::error::Error + Send + Sync + 'static, K::Error: std::error::Error + Send + Sync + 'static,
{ {
self.buf.zeroize(); self.buf.zeroize();
@ -224,7 +222,7 @@ impl<S: ClientStream> AgentClient<S> {
let _ = r.read_string()?; let _ = r.read_string()?;
let mut r = key.reader(0); let mut r = key.reader(0);
if let Some(pk) = K::read(&mut r).map_err(|err| Error::Public(Box::new(err)))? { if let Ok(pk) = K::read(&mut r) {
keys.push(pk); keys.push(pk);
} }
} }
@ -236,7 +234,7 @@ impl<S: ClientStream> AgentClient<S> {
/// Ask the agent to sign the supplied piece of data. /// Ask the agent to sign the supplied piece of data.
pub fn sign_request<K>(&mut self, public: &K, data: Buffer) -> Result<Signature, Error> pub fn sign_request<K>(&mut self, public: &K, data: Buffer) -> Result<Signature, Error>
where where
K: Public + fmt::Debug, K: Encodable + fmt::Debug,
{ {
self.prepare_sign_request(public, &data); self.prepare_sign_request(public, &data);
self.stream.read_response(&mut self.buf)?; self.stream.read_response(&mut self.buf)?;
@ -255,7 +253,7 @@ impl<S: ClientStream> AgentClient<S> {
fn prepare_sign_request<K>(&mut self, public: &K, data: &[u8]) fn prepare_sign_request<K>(&mut self, public: &K, data: &[u8])
where where
K: Public + fmt::Debug, K: Encodable + fmt::Debug,
{ {
// byte SSH_AGENTC_SIGN_REQUEST // byte SSH_AGENTC_SIGN_REQUEST
// string key blob // string key blob
@ -263,10 +261,9 @@ impl<S: ClientStream> AgentClient<S> {
// uint32 flags // uint32 flags
let mut pk = Buffer::default(); let mut pk = Buffer::default();
let n = public.write(&mut pk); public.write(&mut pk);
let total = 1 + n + 4 + data.len() + 4;
debug_assert_eq!(n, pk.len()); let total = 1 + pk.len() + 4 + data.len() + 4;
self.buf.zeroize(); self.buf.zeroize();
self.buf self.buf
@ -294,13 +291,12 @@ impl<S: ClientStream> AgentClient<S> {
/// Ask the agent to remove a key from its memory. /// Ask the agent to remove a key from its memory.
pub fn remove_identity<K>(&mut self, public: &K) -> Result<(), Error> pub fn remove_identity<K>(&mut self, public: &K) -> Result<(), Error>
where where
K: Public, K: Encodable,
{ {
let mut pk: Buffer = Vec::new().into(); let mut pk: Buffer = Vec::new().into();
let n = public.write(&mut pk); public.write(&mut pk);
let total = 1 + n;
debug_assert_eq!(n, pk.len()); let total = 1 + pk.len();
self.buf.zeroize(); self.buf.zeroize();
self.buf.write_u32::<BigEndian>(total as u32)?; self.buf.write_u32::<BigEndian>(total as u32)?;

View File

@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
use std::mem::size_of;
use std::ops::DerefMut; use std::ops::DerefMut;
use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
@ -33,16 +32,15 @@ pub trait Encodable: Sized {
type Error: std::error::Error + Send + Sync + 'static; type Error: std::error::Error + Send + Sync + 'static;
/// Read from the SSH format. /// Read from the SSH format.
fn read_ssh(reader: &mut Cursor) -> Result<Self, Self::Error>; fn read(reader: &mut Cursor) -> Result<Self, Self::Error>;
/// Write to the SSH format. /// Write to the SSH format.
fn write_ssh<E: Encoding>(&self, buf: &mut E); fn write<E: Encoding>(&self, buf: &mut E);
} }
/// Encode in the SSH format. /// Encode in the SSH format.
pub trait Encoding { pub trait Encoding {
/// Push an SSH-encoded string to `self`. /// Push an SSH-encoded string to `self`.
fn extend_ssh_string(&mut self, s: &[u8]) -> usize; fn extend_ssh_string(&mut self, s: &[u8]);
/// Push an SSH-encoded blank string of length `s` to `self`. /// Push an SSH-encoded blank string of length `s` to `self`.
fn extend_ssh_string_blank(&mut self, s: usize) -> &mut [u8]; fn extend_ssh_string_blank(&mut self, s: usize) -> &mut [u8];
/// Push an SSH-encoded multiple-precision integer. /// Push an SSH-encoded multiple-precision integer.
@ -67,11 +65,9 @@ pub fn mpint_len(s: &[u8]) -> usize {
} }
impl Encoding for Vec<u8> { impl Encoding for Vec<u8> {
fn extend_ssh_string(&mut self, s: &[u8]) -> usize { fn extend_ssh_string(&mut self, s: &[u8]) {
self.write_u32::<BigEndian>(s.len() as u32).unwrap(); self.write_u32::<BigEndian>(s.len() as u32).unwrap();
self.extend(s); self.extend(s);
size_of::<u32>() + s.len()
} }
fn extend_ssh_string_blank(&mut self, len: usize) -> &mut [u8] { fn extend_ssh_string_blank(&mut self, len: usize) -> &mut [u8] {
@ -134,7 +130,7 @@ impl Encoding for Vec<u8> {
} }
impl Encoding for Buffer { impl Encoding for Buffer {
fn extend_ssh_string(&mut self, s: &[u8]) -> usize { fn extend_ssh_string(&mut self, s: &[u8]) {
self.deref_mut().extend_ssh_string(s) self.deref_mut().extend_ssh_string(s)
} }

View File

@ -1,26 +0,0 @@
use std::error::Error;
use crate::encoding::{Buffer, Cursor, Encoding};
/// A public SSH key.
pub trait Public: Sized {
type Error: Error + Send + Sync + 'static;
/// Write the public key to the given buffer, in SSH "blob" format.
fn write<E: Encoding>(&self, buf: &mut E) -> usize;
/// Read the public key from the given reader.
fn read(reader: &mut Cursor) -> Result<Option<Self>, Self::Error>;
}
/// A private SSH key.
pub trait Private: Sized {
type Error: Error + Send + Sync + 'static;
/// Read a private key from the given reader.
fn read(reader: &mut Cursor) -> Result<Option<Self>, Self::Error>;
/// Write the key bytes to the supplied buffer.
fn write(&self, buf: &mut Buffer) -> Result<(), Self::Error>;
/// Sign the data and write the signature to the given buffer.
fn write_signature<T: AsRef<[u8]>>(&self, data: T, buf: &mut Buffer)
-> Result<(), Self::Error>;
}

View File

@ -1,3 +1,2 @@
pub mod agent; pub mod agent;
pub mod encoding; pub mod encoding;
pub mod key;

View File

@ -1,17 +1,13 @@
pub mod agent; pub mod agent;
use std::io; use std::io;
use std::ops::DerefMut;
use byteorder::{BigEndian, WriteBytesExt};
use thiserror::Error; use thiserror::Error;
use zeroize::Zeroizing;
use radicle_ssh::encoding; use radicle_ssh::encoding;
use radicle_ssh::encoding::Encodable; use radicle_ssh::encoding::Encodable;
use radicle_ssh::encoding::Encoding; use radicle_ssh::encoding::Encoding;
use radicle_ssh::encoding::Reader; use radicle_ssh::encoding::Reader;
use radicle_ssh::key::Public;
use crate::crypto; use crate::crypto;
use crate::crypto::PublicKey; use crate::crypto::PublicKey;
@ -90,7 +86,7 @@ pub enum SignatureError {
impl Encodable for crypto::Signature { impl Encodable for crypto::Signature {
type Error = SignatureError; type Error = SignatureError;
fn read_ssh(r: &mut encoding::Cursor) -> Result<Self, Self::Error> { fn read(r: &mut encoding::Cursor) -> Result<Self, Self::Error> {
let buf = r.read_string()?; let buf = r.read_string()?;
let mut inner_strs = buf.reader(0); let mut inner_strs = buf.reader(0);
@ -105,7 +101,7 @@ impl Encodable for crypto::Signature {
Ok(sig) Ok(sig)
} }
fn write_ssh<E: Encoding>(&self, buf: &mut E) { fn write<E: Encoding>(&self, buf: &mut E) {
let mut inner_strs = Vec::new(); let mut inner_strs = Vec::new();
inner_strs.extend_ssh_string(b"ssh-ed25519"); inner_strs.extend_ssh_string(b"ssh-ed25519");
inner_strs.extend_ssh_string(self.as_ref()); inner_strs.extend_ssh_string(self.as_ref());
@ -126,12 +122,10 @@ pub enum PublicKeyError {
impl Encodable for PublicKey { impl Encodable for PublicKey {
type Error = PublicKeyError; type Error = PublicKeyError;
fn read_ssh(r: &mut encoding::Cursor) -> Result<Self, Self::Error> { fn read(r: &mut encoding::Cursor) -> Result<Self, Self::Error> {
let buf = r.read_string()?; match r.read_string()? {
let mut str_r = buf.reader(0);
match str_r.read_string()? {
b"ssh-ed25519" => { b"ssh-ed25519" => {
let s = str_r.read_string()?; let s = r.read_string()?;
let p = PublicKey::try_from(s)?; let p = PublicKey::try_from(s)?;
Ok(p) Ok(p)
@ -142,31 +136,11 @@ impl Encodable for PublicKey {
} }
} }
fn write_ssh<E: Encoding>(&self, w: &mut E) { fn write<E: Encoding>(&self, w: &mut E) {
_ = self.write(w);
}
}
impl Public for PublicKey {
type Error = PublicKeyError;
fn read(r: &mut encoding::Cursor) -> Result<Option<Self>, Self::Error> {
match r.read_string()? {
b"ssh-ed25519" => {
let s = r.read_string()?;
let p = PublicKey::try_from(s)?;
Ok(Some(p))
}
_ => Ok(None),
}
}
fn write<E: Encoding>(&self, buf: &mut E) -> usize {
let mut str_w: Vec<u8> = Vec::<u8>::new(); let mut str_w: Vec<u8> = Vec::<u8>::new();
str_w.extend_ssh_string(b"ssh-ed25519"); str_w.extend_ssh_string(b"ssh-ed25519");
str_w.extend_ssh_string(&self[..]); str_w.extend_ssh_string(&self[..]);
buf.extend_ssh_string(&str_w) w.extend_ssh_string(&str_w)
} }
} }
@ -188,14 +162,16 @@ pub enum SecretKeyError {
Crypto(#[from] crypto::Error), Crypto(#[from] crypto::Error),
#[error(transparent)] #[error(transparent)]
Io(#[from] io::Error), Io(#[from] io::Error),
#[error("unknown algorithm '{0}'")]
UnknownAlgorithm(String),
#[error("public key does not match secret key")] #[error("public key does not match secret key")]
Mismatch, Mismatch,
} }
impl radicle_ssh::key::Private for SecretKey { impl Encodable for SecretKey {
type Error = SecretKeyError; type Error = SecretKeyError;
fn read(r: &mut encoding::Cursor) -> Result<Option<Self>, Self::Error> { fn read(r: &mut encoding::Cursor) -> Result<Self, Self::Error> {
match r.read_string()? { match r.read_string()? {
b"ssh-ed25519" => { b"ssh-ed25519" => {
let public = r.read_string()?; let public = r.read_string()?;
@ -206,38 +182,21 @@ impl radicle_ssh::key::Private for SecretKey {
if public != key.public_key().as_ref() { if public != key.public_key().as_ref() {
return Err(SecretKeyError::Mismatch); return Err(SecretKeyError::Mismatch);
} }
Ok(Some(SecretKey(key))) Ok(SecretKey(key))
} }
_ => Ok(None), s => Err(SecretKeyError::UnknownAlgorithm(
String::from_utf8_lossy(s).to_string(),
)),
} }
} }
fn write(&self, buf: &mut Zeroizing<Vec<u8>>) -> Result<(), Self::Error> { fn write<E: Encoding>(&self, buf: &mut E) {
let public = self.0.public_key(); let public = self.0.public_key();
buf.extend_ssh_string(b"ssh-ed25519"); buf.extend_ssh_string(b"ssh-ed25519");
buf.extend_ssh_string(public.as_ref()); buf.extend_ssh_string(public.as_ref());
buf.deref_mut().write_u32::<BigEndian>(64)?; buf.extend_ssh_string(&*self.0);
buf.extend(&*self.0);
buf.extend_ssh_string(b"radicle"); buf.extend_ssh_string(b"radicle");
Ok(())
}
fn write_signature<Bytes: AsRef<[u8]>>(
&self,
data: Bytes,
buf: &mut Zeroizing<Vec<u8>>,
) -> Result<(), Self::Error> {
let name = "ssh-ed25519";
let signature: [u8; 64] = *self.0.sign(data.as_ref(), None);
buf.deref_mut()
.write_u32::<BigEndian>((name.len() + signature.len() + 8) as u32)?;
buf.extend_ssh_string(name.as_bytes());
buf.extend_ssh_string(&signature);
Ok(())
} }
} }
@ -253,12 +212,8 @@ pub enum ExtendedSignatureError {
MissingHeader, MissingHeader,
#[error(transparent)] #[error(transparent)]
Encoding(#[from] encoding::Error), Encoding(#[from] encoding::Error),
#[error("public key encoding")]
PublicKeyEncoding,
#[error(transparent)] #[error(transparent)]
PublicKeyError(#[from] PublicKeyError), PublicKey(#[from] PublicKeyError),
#[error("signature encoding")]
SignatureEncoding,
#[error(transparent)] #[error(transparent)]
SignatureError(#[from] SignatureError), SignatureError(#[from] SignatureError),
#[error("unsupported version '{0}'")] #[error("unsupported version '{0}'")]
@ -270,8 +225,8 @@ pub enum ExtendedSignatureError {
/// See <https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig> /// See <https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig>
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ExtendedSignature { pub struct ExtendedSignature {
sig_version: u32, version: u32,
publickey: crypto::PublicKey, public_key: crypto::PublicKey,
/// Unambigious interpretation domain to prevent cross-protocol attacks. /// Unambigious interpretation domain to prevent cross-protocol attacks.
namespace: Vec<u8>, namespace: Vec<u8>,
reserved: Vec<u8>, reserved: Vec<u8>,
@ -283,31 +238,30 @@ pub struct ExtendedSignature {
impl Encodable for ExtendedSignature { impl Encodable for ExtendedSignature {
type Error = ExtendedSignatureError; type Error = ExtendedSignatureError;
fn read_ssh(r: &mut encoding::Cursor) -> Result<Self, Self::Error> { fn read(r: &mut encoding::Cursor) -> Result<Self, Self::Error> {
let sig_version = r.read_u32()?; let sig_version = r.read_u32()?;
if sig_version > 1 { if sig_version > 1 {
return Err(ExtendedSignatureError::UnsupportedVersion(sig_version)); return Err(ExtendedSignatureError::UnsupportedVersion(sig_version));
} }
let mut pk = r.read_string()?.reader(0);
Ok(ExtendedSignature { Ok(ExtendedSignature {
sig_version, version: sig_version,
publickey: PublicKey::read_ssh(r) public_key: PublicKey::read(&mut pk)?,
.map_err(|_| ExtendedSignatureError::PublicKeyEncoding)?,
namespace: r.read_string()?.into(), namespace: r.read_string()?.into(),
reserved: r.read_string()?.into(), reserved: r.read_string()?.into(),
hash_algorithm: r.read_string()?.into(), hash_algorithm: r.read_string()?.into(),
signature: crypto::Signature::read_ssh(r) signature: crypto::Signature::read(r)?,
.map_err(|_| ExtendedSignatureError::PublicKeyEncoding)?,
}) })
} }
fn write_ssh<E: Encoding>(&self, buf: &mut E) { fn write<E: Encoding>(&self, buf: &mut E) {
buf.extend_u32(self.sig_version); buf.extend_u32(self.version);
let _ = &self.publickey.write_ssh(buf); let _ = &self.public_key.write(buf);
buf.extend_ssh_string(&self.namespace); buf.extend_ssh_string(&self.namespace);
buf.extend_ssh_string(&self.reserved); buf.extend_ssh_string(&self.reserved);
buf.extend_ssh_string(&self.hash_algorithm); buf.extend_ssh_string(&self.hash_algorithm);
let _ = &self.signature.write_ssh(buf); let _ = &self.signature.write(buf);
} }
} }
@ -334,27 +288,24 @@ impl ExtendedSignature {
return Err(ExtendedSignatureError::MagicPreamble(preamble)); return Err(ExtendedSignatureError::MagicPreamble(preamble));
} }
let sig = ExtendedSignature::read_ssh(&mut reader)?; let sig = ExtendedSignature::read(&mut reader)?;
Ok(sig) Ok(sig)
} }
pub fn to_armored(&self) -> Vec<u8> { pub fn to_armored(&self) -> Vec<u8> {
let mut v = encoding::Buffer::default(); let mut buf = encoding::Buffer::from(Self::MAGIC_PREAMBLE.to_vec());
self.write(&mut buf);
v.extend(Self::MAGIC_PREAMBLE);
self.write_ssh(&mut v);
let mut armored = Self::ARMORED_HEADER.to_vec(); let mut armored = Self::ARMORED_HEADER.to_vec();
armored.push(b'\n'); armored.push(b'\n');
let body: Vec<u8> = base64::encode(v).into(); let body = base64::encode(buf);
for line in body.chunks(Self::ARMORED_WIDTH) { for line in body.as_bytes().chunks(Self::ARMORED_WIDTH) {
armored.extend(line.to_vec()); armored.extend(line);
armored.push(b'\n'); armored.push(b'\n');
} }
armored.extend(Self::ARMORED_FOOTER); armored.extend(Self::ARMORED_FOOTER);
armored armored
} }
} }
@ -366,13 +317,12 @@ mod test {
use quickcheck_macros::quickcheck; use quickcheck_macros::quickcheck;
use zeroize::Zeroizing; use zeroize::Zeroizing;
use super::{ExtendedSignature, SecretKey}; use super::{fmt, ExtendedSignature, SecretKey};
use crate::crypto;
use crate::crypto::PublicKey; use crate::crypto::PublicKey;
use crate::crypto::{self};
use crate::test::arbitrary::ByteArray; use crate::test::arbitrary::ByteArray;
use radicle_ssh::agent::client::{AgentClient, ClientStream, Error}; use radicle_ssh::agent::client::{AgentClient, ClientStream, Error};
use radicle_ssh::encoding::Reader; use radicle_ssh::encoding::*;
use radicle_ssh::key::Private as _;
#[derive(Clone, Default)] #[derive(Clone, Default)]
struct DummyStream { struct DummyStream {
@ -396,12 +346,12 @@ mod test {
#[quickcheck] #[quickcheck]
fn prop_encode_decode_sk(input: ByteArray<64>) { fn prop_encode_decode_sk(input: ByteArray<64>) {
let mut buf = Vec::new().into(); let mut buf = Buffer::default();
let sk = crypto::SecretKey::new(input.into_inner()); let sk = crypto::SecretKey::new(input.into_inner());
SecretKey(sk).write(&mut buf).unwrap(); SecretKey(sk).write(&mut buf);
let mut cursor = buf.reader(0); let mut cursor = buf.reader(0);
let output = SecretKey::read(&mut cursor).unwrap().unwrap(); let output = SecretKey::read(&mut cursor).unwrap();
assert_eq!(sk, output.0); assert_eq!(sk, output.0);
} }
@ -469,16 +419,43 @@ mod test {
let armored: &[u8] = b"-----BEGIN SSH SIGNATURE----- let armored: &[u8] = b"-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgvjrQogRxxLjzzWns8+mKJAGzEX U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgvjrQogRxxLjzzWns8+mKJAGzEX
4fm2ALoN7pyvD2ttQAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 4fm2ALoN7pyvD2ttQAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
AAAAQJ759x+pFz0z2yM13S/sqeOOSgTE3fhoJG54dotNTk17dQEPKnH4S4N5jjA+pxM1mb AAAAQI84aPZsXxlQigpy1/Y/iJSmHSS//CIgvqvUMQIb/TM2vhCKruduH0cK02k9G8wOI+
oejZ0WJ0cQtBjWZ7JEBQM= EUMf2bSDyxbJyZThOEiAs=
-----END SSH SIGNATURE-----"; -----END SSH SIGNATURE-----";
let public_key = "AAAAC3NzaC1lZDI1NTE5AAAAIL460KIEccS4881p7PPpiiQBsxF+H5tgC6De6crw9rbU";
let signature = ExtendedSignature::from_armored(armored).unwrap(); let signature = ExtendedSignature::from_armored(armored).unwrap();
assert_eq!(1, signature.sig_version);
assert_eq!(signature.version, 1);
assert_eq!(fmt::key(&signature.public_key), public_key);
assert_eq!( assert_eq!(
String::from_utf8(armored.to_vec()), String::from_utf8(armored.to_vec()),
String::from_utf8(signature.to_armored()), String::from_utf8(signature.to_armored()),
"signature should remain unaltered after decoding" "signature should remain unaltered after decoding"
); );
} }
#[test]
fn test_signature_verify() {
let seed = crypto::Seed::new([1; 32]);
let pair = crypto::KeyPair::from_seed(seed);
let message = &[0xff];
let sig = pair.sk.sign(message, None);
let esig = ExtendedSignature {
version: 1,
public_key: pair.pk.into(),
signature: sig.into(),
hash_algorithm: vec![],
namespace: vec![],
reserved: vec![],
};
let armored = esig.to_armored();
let unarmored = ExtendedSignature::from_armored(&armored).unwrap();
unarmored
.public_key
.verify(message, &unarmored.signature)
.unwrap();
}
} }