Skip to content

Commit f0ba88d

Browse files
authored
Merge pull request #101 from wiktor-k/wiktor-k/fix-support-for-certs
Add support for certificates in requests and responses
2 parents aee26b3 + 4c6c1a1 commit f0ba88d

12 files changed

Lines changed: 111 additions & 25 deletions

File tree

.justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env -S just --working-directory . --justfile
22
# Load project-specific properties from the `.env` file
33

4-
set dotenv-load := true
4+
set dotenv-load
55

66
# Since this is a first recipe it's being run by default.
77
# Faster checks need to be executed first for better UX. For example

examples/agent-socket-info.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ impl Session for AgentSocketInfo {
2727
async fn request_identities(&mut self) -> Result<Vec<Identity>, AgentError> {
2828
Ok(vec![Identity {
2929
// this is just a dummy key, the comment is important
30-
pubkey: KeyData::Ed25519(ssh_key::public::Ed25519PublicKey([0; 32])).into(),
30+
credential: KeyData::Ed25519(ssh_key::public::Ed25519PublicKey([0; 32])).into(),
3131
comment: self.comment.clone(),
3232
}])
3333
}

examples/key-storage.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ impl KeyStorage {
7474
#[crate::async_trait]
7575
impl Session for KeyStorage {
7676
async fn sign(&mut self, sign_request: SignRequest) -> Result<Signature, AgentError> {
77-
let pubkey: PublicKey = sign_request.pubkey.key_data().clone().into();
77+
let pubkey: PublicKey = sign_request.credential.key_data().clone().into();
7878

7979
if let Some(identity) = self.identity_from_pubkey(&pubkey) {
8080
match identity.privkey.key_data() {
@@ -122,7 +122,7 @@ impl Session for KeyStorage {
122122
let mut identities = vec![];
123123
for identity in self.identities.lock().unwrap().iter() {
124124
identities.push(message::Identity {
125-
pubkey: identity.pubkey.key_data().clone().into(),
125+
credential: identity.pubkey.key_data().clone().into(),
126126
comment: identity.comment.clone(),
127127
})
128128
}
@@ -175,7 +175,7 @@ impl Session for KeyStorage {
175175
}
176176

177177
async fn remove_identity(&mut self, identity: RemoveIdentity) -> Result<(), AgentError> {
178-
let pubkey: PublicKey = identity.pubkey.into();
178+
let pubkey: PublicKey = identity.credential.key_data().clone().into();
179179
self.identity_remove(&pubkey)?;
180180
Ok(())
181181
}

examples/random-key.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ impl RandomKey {
4141
impl Session for RandomKey {
4242
async fn sign(&mut self, sign_request: SignRequest) -> Result<Signature, AgentError> {
4343
let private_key = self.private_key.lock().unwrap();
44-
let PublicCredential::Key(pubkey) = sign_request.pubkey else {
44+
let PublicCredential::Key(pubkey) = sign_request.credential else {
4545
return Err(std::io::Error::other("Key not found").into());
4646
};
4747
if PublicKey::from(private_key.deref()).key_data() != &pubkey {
@@ -88,7 +88,7 @@ impl Session for RandomKey {
8888
async fn request_identities(&mut self) -> Result<Vec<Identity>, AgentError> {
8989
let identity = self.private_key.lock().unwrap();
9090
Ok(vec![Identity {
91-
pubkey: PublicCredential::Key(PublicKey::from(identity.deref()).into()),
91+
credential: PublicCredential::Key(PublicKey::from(identity.deref()).into()),
9292
comment: identity.comment().into(),
9393
}])
9494
}

src/proto/message/add_remove.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ pub use constrained::*;
66
use secrecy::ExposeSecret as _;
77
use secrecy::SecretString;
88
use ssh_encoding::{self, CheckedSum, Decode, Encode, Reader, Writer};
9-
use ssh_key::public::KeyData;
109

1110
use super::PrivateCredential;
11+
use crate::proto::PublicCredential;
1212
use crate::proto::{Error, Result};
1313

1414
/// Add a key to an agent.
@@ -101,25 +101,25 @@ impl PartialEq for SmartcardKey {
101101
#[derive(Clone, PartialEq, Debug)]
102102
pub struct RemoveIdentity {
103103
/// The public key portion of the [`Identity`](super::Identity) to be removed
104-
pub pubkey: KeyData,
104+
pub credential: PublicCredential,
105105
}
106106

107107
impl Decode for RemoveIdentity {
108108
type Error = Error;
109109

110110
fn decode(reader: &mut impl Reader) -> Result<Self> {
111-
let pubkey = reader.read_prefixed(KeyData::decode)?;
111+
let credential = reader.read_prefixed(PublicCredential::decode)?;
112112

113-
Ok(Self { pubkey })
113+
Ok(Self { credential })
114114
}
115115
}
116116

117117
impl Encode for RemoveIdentity {
118118
fn encoded_len(&self) -> ssh_encoding::Result<usize> {
119-
self.pubkey.encoded_len_prefixed()
119+
self.credential.encoded_len_prefixed()
120120
}
121121

122122
fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
123-
self.pubkey.encode_prefixed(writer)
123+
self.credential.encode_prefixed(writer)
124124
}
125125
}

src/proto/message/credential.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,23 @@ impl Decode for PublicCredential {
140140
type Error = Error;
141141

142142
fn decode(reader: &mut impl Reader) -> core::result::Result<Self, Self::Error> {
143-
// TODO: implement parsing certificates
144-
Ok(Self::Key(KeyData::decode(reader)?))
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+
}
145160
}
146161
}
147162

src/proto/message/identity.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::proto::{Error, Result};
1313
#[derive(Clone, PartialEq, Debug)]
1414
pub struct Identity {
1515
/// A standard public-key encoding of an underlying key.
16-
pub pubkey: PublicCredential,
16+
pub credential: PublicCredential,
1717

1818
/// A human-readable comment
1919
pub comment: String,
@@ -36,24 +36,27 @@ impl Decode for Identity {
3636
type Error = Error;
3737

3838
fn decode(reader: &mut impl Reader) -> Result<Self> {
39-
let pubkey = reader.read_prefixed(PublicCredential::decode)?;
39+
let credential = reader.read_prefixed(PublicCredential::decode)?;
4040
let comment = String::decode(reader)?;
4141

42-
Ok(Self { pubkey, comment })
42+
Ok(Self {
43+
credential,
44+
comment,
45+
})
4346
}
4447
}
4548

4649
impl Encode for Identity {
4750
fn encoded_len(&self) -> ssh_encoding::Result<usize> {
4851
[
49-
self.pubkey.encoded_len_prefixed()?,
52+
self.credential.encoded_len_prefixed()?,
5053
self.comment.encoded_len()?,
5154
]
5255
.checked_sum()
5356
}
5457

5558
fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
56-
self.pubkey.encode_prefixed(writer)?;
59+
self.credential.encode_prefixed(writer)?;
5760
self.comment.encode(writer)?;
5861

5962
Ok(())

src/proto/message/request.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,71 @@ impl Encode for Request {
148148
Ok(())
149149
}
150150
}
151+
152+
#[cfg(test)]
153+
mod tests {
154+
use ssh_key::Algorithm;
155+
use testresult::TestResult;
156+
157+
use super::*;
158+
use crate::proto::{PublicCredential, Response};
159+
160+
#[test]
161+
fn sign_with_cert() -> TestResult {
162+
let req =
163+
Request::decode(&mut &std::fs::read("tests/messages/req-sign-with-cert.bin")?[..])?;
164+
let Request::SignRequest(req) = req else {
165+
panic!("expected SignRequest");
166+
};
167+
let PublicCredential::Cert(cert) = req.credential else {
168+
panic!("expected certificate");
169+
};
170+
assert_eq!(cert.algorithm(), Algorithm::Rsa { hash: None });
171+
Ok(())
172+
}
173+
174+
#[test]
175+
fn sign_with_pubkey() -> TestResult {
176+
let req = Request::decode(&mut &std::fs::read("tests/messages/req-sign-request.bin")?[..])?;
177+
let Request::SignRequest(req) = req else {
178+
panic!("expected SignRequest");
179+
};
180+
let PublicCredential::Key(cert) = req.credential else {
181+
panic!("expected key");
182+
};
183+
assert_eq!(cert.algorithm(), Algorithm::Rsa { hash: None });
184+
Ok(())
185+
}
186+
187+
#[test]
188+
fn resp_identities_with_cert() -> TestResult {
189+
let resp = Response::decode(
190+
&mut &std::fs::read("tests/messages/resp-identities-with-cert.bin")?[..],
191+
)?;
192+
let Response::IdentitiesAnswer(resp) = resp else {
193+
panic!("expected IdentitiesAnswer");
194+
};
195+
assert_eq!(resp.len(), 4);
196+
let PublicCredential::Cert(cert) = &resp[3].credential else {
197+
panic!("expected certificate");
198+
};
199+
assert_eq!(cert.algorithm(), Algorithm::Rsa { hash: None });
200+
Ok(())
201+
}
202+
203+
#[test]
204+
fn resp_identities_with_key() -> TestResult {
205+
let resp = Response::decode(
206+
&mut &std::fs::read("tests/messages/resp-identities-answer.bin")?[..],
207+
)?;
208+
let Response::IdentitiesAnswer(resp) = resp else {
209+
panic!("expected IdentitiesAnswer");
210+
};
211+
assert_eq!(resp.len(), 1);
212+
let PublicCredential::Key(key) = &resp[0].credential else {
213+
panic!("expected key");
214+
};
215+
assert_eq!(key.algorithm(), Algorithm::Rsa { hash: None });
216+
Ok(())
217+
}
218+
}

src/proto/message/sign.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::proto::{Error, Result};
1313
#[derive(Clone, PartialEq, Debug)]
1414
pub struct SignRequest {
1515
/// The public key portion of the [`Identity`](super::Identity) in the agent to sign the data with
16-
pub pubkey: PublicCredential,
16+
pub credential: PublicCredential,
1717

1818
/// Binary data to be signed
1919
pub data: Vec<u8>,
@@ -32,7 +32,7 @@ impl Decode for SignRequest {
3232
let flags = u32::decode(reader)?;
3333

3434
Ok(Self {
35-
pubkey,
35+
credential: pubkey,
3636
data,
3737
flags,
3838
})
@@ -42,15 +42,15 @@ impl Decode for SignRequest {
4242
impl Encode for SignRequest {
4343
fn encoded_len(&self) -> ssh_encoding::Result<usize> {
4444
[
45-
self.pubkey.encoded_len_prefixed()?,
45+
self.credential.encoded_len_prefixed()?,
4646
self.data.encoded_len()?,
4747
self.flags.encoded_len()?,
4848
]
4949
.checked_sum()
5050
}
5151

5252
fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
53-
self.pubkey.encode_prefixed(writer)?;
53+
self.credential.encode_prefixed(writer)?;
5454
self.data.encode(writer)?;
5555
self.flags.encode(writer)?;
5656

File renamed without changes.

0 commit comments

Comments
 (0)