Skip to main content

ssh_agent_lib/proto/message/
credential.rs

1//! A container for a public / private key pair, or a certificate / private key.
2
3use std::str::FromStr as _;
4
5use ssh_encoding::{self, CheckedSum, Decode, Encode, Reader, Writer};
6use ssh_key::public::KeyData;
7use ssh_key::{certificate::Certificate, private::KeypairData, Algorithm};
8
9use crate::proto::{Error, PrivateKeyData, Result};
10
11/// A container for a public / private key pair, or a certificate / private key.
12///
13/// When adding an identity to an agent, a user can provide either:
14/// 1. A public / private key pair
15/// 2. An OpenSSH [certificate](https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.certkeys)
16///
17/// This structure covers both types of identities a user may
18/// send to an agent as part of a [`Request::AddIdentity`](crate::proto::Request::AddIdentity) message.
19#[derive(Clone, PartialEq, Debug)]
20pub enum PrivateCredential {
21    /// A public/private key pair
22    Key {
23        /// Public/private key pair data
24        privkey: KeypairData,
25
26        /// Key comment, if any.
27        comment: String,
28    },
29
30    /// An OpenSSH [certificate](https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.certkeys)
31    Cert {
32        /// Certificate algorithm.
33        algorithm: Algorithm,
34
35        /// Certificate data.
36        certificate: Box<Certificate>,
37
38        /// Private key data.
39        privkey: PrivateKeyData,
40
41        /// Comment, if any.
42        comment: String,
43    },
44}
45
46impl Decode for PrivateCredential {
47    type Error = Error;
48
49    fn decode(reader: &mut impl Reader) -> Result<Self> {
50        let alg = String::decode(reader)?;
51        let cert_alg = Algorithm::new_certificate(&alg);
52
53        if let Ok(algorithm) = cert_alg {
54            let certificate = reader
55                .read_prefixed(|reader| {
56                    let cert = Certificate::decode(reader)?;
57                    Ok::<_, Error>(cert)
58                })?
59                .into();
60            let privkey = PrivateKeyData::decode_as(reader, algorithm.clone())?;
61            let comment = String::decode(reader)?;
62
63            Ok(PrivateCredential::Cert {
64                algorithm,
65                certificate,
66                privkey,
67                comment,
68            })
69        } else {
70            let algorithm = Algorithm::from_str(&alg).map_err(ssh_encoding::Error::from)?;
71            let privkey = KeypairData::decode_as(reader, algorithm)?;
72            let comment = String::decode(reader)?;
73            Ok(PrivateCredential::Key { privkey, comment })
74        }
75    }
76}
77
78impl Encode for PrivateCredential {
79    fn encoded_len(&self) -> ssh_encoding::Result<usize> {
80        match self {
81            Self::Key { privkey, comment } => {
82                [privkey.encoded_len()?, comment.encoded_len()?].checked_sum()
83            }
84            Self::Cert {
85                algorithm,
86                certificate,
87                privkey,
88                comment,
89            } => [
90                algorithm.to_certificate_type().encoded_len()?,
91                certificate.encoded_len_prefixed()?,
92                privkey.encoded_len()?,
93                comment.encoded_len()?,
94            ]
95            .checked_sum(),
96        }
97    }
98
99    fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
100        match self {
101            Self::Key { privkey, comment } => {
102                privkey.encode(writer)?;
103                comment.encode(writer)
104            }
105            Self::Cert {
106                algorithm,
107                certificate,
108                privkey,
109                comment,
110            } => {
111                algorithm.to_certificate_type().encode(writer)?;
112                certificate.encode_prefixed(writer)?;
113                privkey.encode(writer)?;
114                comment.encode(writer)
115            }
116        }
117    }
118}
119
120#[derive(Debug, PartialEq, Eq, Clone)]
121/// Represents a public credential.
122pub enum PublicCredential {
123    /// Plain public key.
124    Key(KeyData),
125    /// Signed public key.
126    Cert(Box<Certificate>),
127}
128
129impl PublicCredential {
130    /// Returns a reference to the [KeyData].
131    pub fn key_data(&self) -> &KeyData {
132        match self {
133            Self::Key(key) => key,
134            Self::Cert(cert) => cert.public_key(),
135        }
136    }
137}
138
139impl Decode for PublicCredential {
140    type Error = Error;
141
142    fn decode(reader: &mut impl Reader) -> core::result::Result<Self, Self::Error> {
143        // FIXME: This needs to be rewritten using Certificate::decode_as when ssh-key 0.7.0 hits stable, see: https://github.com/wiktor-k/ssh-agent-lib/pull/85#issuecomment-3751946208
144        let alg = String::decode(reader)?;
145
146        let remaining_len = reader.remaining_len();
147        let mut buf = Vec::with_capacity(4 + alg.len() + remaining_len);
148        alg.encode(&mut buf)?;
149        let mut tail = vec![0u8; remaining_len];
150        reader.read(&mut tail)?;
151        buf.extend_from_slice(&tail);
152
153        if Algorithm::new_certificate(&alg).is_ok() {
154            let cert = Certificate::decode(&mut &buf[..])?;
155            Ok(Self::Cert(Box::new(cert)))
156        } else {
157            let key = KeyData::decode(&mut &buf[..])?;
158            Ok(Self::Key(key))
159        }
160    }
161}
162
163impl Encode for PublicCredential {
164    fn encoded_len(&self) -> std::result::Result<usize, ssh_encoding::Error> {
165        match self {
166            Self::Key(pubkey) => pubkey.encoded_len(),
167            Self::Cert(certificate) => certificate.encoded_len(),
168        }
169    }
170
171    fn encode(
172        &self,
173        writer: &mut impl ssh_encoding::Writer,
174    ) -> std::result::Result<(), ssh_encoding::Error> {
175        match self {
176            Self::Key(pubkey) => pubkey.encode(writer),
177            Self::Cert(certificate) => certificate.encode(writer),
178        }
179    }
180}
181
182impl From<KeyData> for PublicCredential {
183    fn from(value: KeyData) -> Self {
184        Self::Key(value)
185    }
186}
187
188impl From<Certificate> for PublicCredential {
189    fn from(value: Certificate) -> Self {
190        Self::Cert(value.into())
191    }
192}