Skip to content

Commit 81105ce

Browse files
committed
Use install-action for installing just
See: casey/just#2013 Signed-off-by: Wiktor Kwapisiewicz <wiktor@metacode.biz>
1 parent 5f73c79 commit 81105ce

File tree

5 files changed

+184
-73
lines changed

5 files changed

+184
-73
lines changed

.github/workflows/misc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
runs-on: ${{ matrix.os }}
2828
steps:
2929
- uses: actions/checkout@v4
30-
- run: cargo install --locked just
30+
- uses: taiki-e/install-action@just
3131
- run: just install-packages
3232
# If the example doesn't compile the integration test will
3333
# be stuck. Check for compilation issues earlier to abort the job

.github/workflows/rust.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
runs-on: ${{ matrix.os }}
3333
steps:
3434
- uses: actions/checkout@v4
35-
- uses: wiktor-k/setup-just@v1
35+
- uses: taiki-e/install-action@just
3636
- run: rustup install nightly
3737
- run: rustup component add rustfmt --toolchain nightly
3838
- name: Check formatting
@@ -49,7 +49,7 @@ jobs:
4949
runs-on: ${{ matrix.os }}
5050
steps:
5151
- uses: actions/checkout@v4
52-
- uses: wiktor-k/setup-just@v1
52+
- uses: taiki-e/install-action@just
5353
- run: just install-packages
5454
- name: Run unit tests
5555
run: just tests
@@ -73,7 +73,7 @@ jobs:
7373
runs-on: ${{ matrix.os }}
7474
steps:
7575
- uses: actions/checkout@v4
76-
- uses: wiktor-k/setup-just@v1
76+
- uses: taiki-e/install-action@just
7777
- run: just install-packages
7878
- name: Check for lints
7979
run: just lints

Cargo.lock

Lines changed: 71 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,5 @@ rstest = "0.18.2"
5757
openpgp-card = "0.4.2"
5858
card-backend-pcsc = "0.5.0"
5959
clap = { version = "4.5.4", features = ["derive"] }
60+
secrecy = "0.8.0"
61+
retainer = "0.3.0"

examples/openpgp-card-agent.rs

Lines changed: 107 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@
1414
//! - storing PIN for one of the cards: `SSH_AUTH_SOCK=/tmp/sock ssh-add -s 0006:15422467` (the agent will validate the PIN before storing it)
1515
//! - and that's it! You can use the agent to login to your SSH servers.
1616
17-
use std::{
18-
collections::HashMap,
19-
sync::{Arc, Mutex},
20-
};
17+
use std::{sync::Arc, time::Duration};
2118

2219
use card_backend_pcsc::PcscBackend;
2320
use clap::Parser;
@@ -26,11 +23,13 @@ use openpgp_card::{
2623
crypto_data::{EccType, PublicKeyMaterial},
2724
Card, KeyType,
2825
};
26+
use retainer::{Cache, CacheExpiration};
27+
use secrecy::{ExposeSecret, SecretString};
2928
use service_binding::Binding;
3029
use ssh_agent_lib::{
3130
agent::Session,
3231
error::AgentError,
33-
proto::{Identity, SignRequest, SmartcardKey},
32+
proto::{AddSmartcardKeyConstrained, Identity, KeyConstraint, SignRequest, SmartcardKey},
3433
Agent,
3534
};
3635
use ssh_key::{
@@ -39,9 +38,17 @@ use ssh_key::{
3938
};
4039
use testresult::TestResult;
4140

42-
#[derive(Default)]
4341
struct CardAgent {
44-
pwds: Arc<Mutex<HashMap<String, String>>>,
42+
pwds: Arc<Cache<String, SecretString>>,
43+
}
44+
45+
impl CardAgent {
46+
pub fn new() -> Self {
47+
let pwds: Arc<Cache<String, SecretString>> = Arc::new(Default::default());
48+
let clone = Arc::clone(&pwds);
49+
tokio::spawn(async move { clone.monitor(4, 0.25, Duration::from_secs(3)).await });
50+
Self { pwds }
51+
}
4552
}
4653

4754
impl Agent for CardAgent {
@@ -53,7 +60,74 @@ impl Agent for CardAgent {
5360
}
5461

5562
struct CardSession {
56-
pwds: Arc<Mutex<HashMap<String, String>>>,
63+
pwds: Arc<Cache<String, SecretString>>,
64+
}
65+
66+
impl CardSession {
67+
async fn handle_sign(
68+
&self,
69+
request: SignRequest,
70+
) -> Result<ssh_key::Signature, Box<dyn std::error::Error + Send + Sync>> {
71+
let cards = PcscBackend::cards(None).map_err(AgentError::other)?;
72+
for card in cards {
73+
let mut card = Card::new(card?)?;
74+
let mut tx = card.transaction()?;
75+
let ident = tx.application_identifier()?.ident();
76+
if let PublicKeyMaterial::E(e) = tx.public_key(KeyType::Authentication)? {
77+
if let AlgorithmAttributes::Ecc(ecc) = e.algo() {
78+
if ecc.ecc_type() == EccType::EdDSA {
79+
let pubkey = KeyData::Ed25519(Ed25519PublicKey(e.data().try_into()?));
80+
if pubkey == request.pubkey {
81+
let pin = self.pwds.get(&ident).await;
82+
return if let Some(pin) = pin {
83+
tx.verify_pw1_user(pin.expose_secret().as_bytes())?;
84+
let signature = tx.internal_authenticate(request.data.clone())?;
85+
86+
Ok(Signature::new(Algorithm::Ed25519, signature)?)
87+
} else {
88+
// no pin saved, use "ssh-add -s ..."
89+
Err(std::io::Error::other("no pin saved").into())
90+
};
91+
}
92+
}
93+
}
94+
}
95+
}
96+
Err(std::io::Error::other("no applicable card found").into())
97+
}
98+
99+
async fn handle_add_smartcard_key(
100+
&mut self,
101+
key: SmartcardKey,
102+
expiration: impl Into<CacheExpiration>,
103+
) -> Result<(), AgentError> {
104+
match PcscBackend::cards(None) {
105+
Ok(cards) => {
106+
let card_pin_matches = cards
107+
.flat_map(|card| {
108+
let mut card = Card::new(card?)?;
109+
let mut tx = card.transaction()?;
110+
let ident = tx.application_identifier()?.ident();
111+
if ident == key.id {
112+
tx.verify_pw1_user(key.pin.as_bytes())?;
113+
Ok::<_, Box<dyn std::error::Error>>(true)
114+
} else {
115+
Ok(false)
116+
}
117+
})
118+
.any(|x| x);
119+
if card_pin_matches {
120+
self.pwds.insert(key.id, key.pin.into(), expiration).await;
121+
Ok(())
122+
} else {
123+
Err(AgentError::IO(std::io::Error::other(
124+
"Card/PIN combination is not valid",
125+
)))
126+
}
127+
}
128+
Err(error) => Err(AgentError::other(error)),
129+
}
130+
}
57131
}
58132

59133
#[ssh_agent_lib::async_trait]
@@ -87,70 +161,32 @@ impl Session for CardSession {
87161
}
88162

89163
async fn add_smartcard_key(&mut self, key: SmartcardKey) -> Result<(), AgentError> {
90-
match PcscBackend::cards(None) {
91-
Ok(cards) => {
92-
let card_pin_matches = cards
93-
.flat_map(|card| {
94-
let mut card = Card::new(card?)?;
95-
let mut tx = card.transaction()?;
96-
let ident = tx.application_identifier()?.ident();
97-
if ident == key.id {
98-
tx.verify_pw1_user(key.pin.as_bytes())?;
99-
Ok::<_, Box<dyn std::error::Error>>(true)
100-
} else {
101-
Ok(false)
102-
}
103-
})
104-
.any(|x| x);
105-
if card_pin_matches {
106-
self.pwds
107-
.lock()
108-
.expect("lock not to be poisoned")
109-
.insert(key.id, key.pin);
110-
Ok(())
111-
} else {
112-
Err(AgentError::IO(std::io::Error::other(
113-
"Card/PIN combination is not valid",
114-
)))
115-
}
116-
}
117-
Err(error) => Err(AgentError::other(error)),
164+
self.handle_add_smartcard_key(key, CacheExpiration::none())
165+
.await
166+
}
167+
168+
async fn add_smartcard_key_constrained(
169+
&mut self,
170+
key: AddSmartcardKeyConstrained,
171+
) -> Result<(), AgentError> {
172+
if key.constraints.len() > 1 {
173+
return Err(AgentError::other(std::io::Error::other(
174+
"Only one lifetime constraint supported.",
175+
)));
118176
}
177+
let expiration_in_seconds = if let KeyConstraint::Lifetime(seconds) = key.constraints[0] {
178+
Duration::from_secs(seconds as u64)
179+
} else {
180+
return Err(AgentError::other(std::io::Error::other(
181+
"Only one lifetime constraint supported.",
182+
)));
183+
};
184+
self.handle_add_smartcard_key(key.key, expiration_in_seconds)
185+
.await
119186
}
120187

121188
async fn sign(&mut self, request: SignRequest) -> Result<Signature, AgentError> {
122-
let cards = PcscBackend::cards(None).map_err(AgentError::other)?;
123-
cards
124-
.flat_map(|card| -> Result<_, Box<dyn std::error::Error>> {
125-
let mut card = Card::new(card?)?;
126-
let mut tx = card.transaction()?;
127-
let ident = tx.application_identifier()?.ident();
128-
if let PublicKeyMaterial::E(e) = tx.public_key(KeyType::Authentication)? {
129-
if let AlgorithmAttributes::Ecc(ecc) = e.algo() {
130-
if ecc.ecc_type() == EccType::EdDSA {
131-
let pubkey = KeyData::Ed25519(Ed25519PublicKey(e.data().try_into()?));
132-
if pubkey == request.pubkey {
133-
let pwds = self.pwds.lock().expect("mutex not to be poisoned");
134-
let pin = pwds.get(&ident);
135-
return if let Some(pin) = pin {
136-
tx.verify_pw1_user(pin.as_bytes())?;
137-
let signature =
138-
tx.internal_authenticate(request.data.clone())?;
139-
140-
Ok(Signature::new(Algorithm::Ed25519, signature))
141-
} else {
142-
// no pin saved, use "ssh-add -s ..."
143-
Err(std::io::Error::other("no pin saved").into())
144-
};
145-
}
146-
}
147-
}
148-
}
149-
Err(std::io::Error::other("no applicable card found").into())
150-
})
151-
.flatten()
152-
.next()
153-
.ok_or(std::io::Error::other("no applicable card found").into())
189+
self.handle_sign(request).await.map_err(AgentError::Other)
154190
}
155191
}
156192

@@ -162,7 +198,9 @@ struct Args {
162198

163199
#[tokio::main]
164200
async fn main() -> TestResult {
201+
env_logger::init();
202+
165203
let args = Args::parse();
166-
CardAgent::default().bind(args.host.try_into()?).await?;
204+
CardAgent::new().bind(args.host.try_into()?).await?;
167205
Ok(())
168206
}

0 commit comments

Comments
 (0)