Skip to content
This repository was archived by the owner on Feb 11, 2025. It is now read-only.

Commit a6d246b

Browse files
Merge pull request #330 from VishnuJin/feat-label-search-for-sign-invoice
feat - add label search argument to sign-invoice
2 parents f7e31cc + ab0b9b2 commit a6d246b

File tree

7 files changed

+76
-21
lines changed

7 files changed

+76
-21
lines changed

bin/client/main.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use bindle::invoice::signature::{
1111
};
1212
use bindle::invoice::Invoice;
1313
use bindle::provider::ProviderError;
14-
use bindle::signature::{KeyEntry, KeyRingLoader, KeyRingSaver};
14+
use bindle::signature::{KeyEntry, KeyRingLoader, KeyRingSaver, LabelMatch};
1515
use bindle::standalone::{StandaloneRead, StandaloneWrite};
1616
use bindle::SignatureError;
1717
use bindle::{
@@ -246,8 +246,17 @@ async fn run() -> std::result::Result<(), ClientError> {
246246
None => ensure_config_dir().await?.join("secret_keys.toml"),
247247
};
248248

249+
let match_type = match (sign_opts.label, sign_opts.label_matching) {
250+
(Some(label), None) => Some(LabelMatch::FullMatch(label)),
251+
(None, Some(label_matching)) => Some(LabelMatch::PartialMatch(label_matching)),
252+
(None, None) => None,
253+
_ => {
254+
unreachable!("both label and label-matching cannot be present at the same time")
255+
}
256+
};
257+
249258
// Signing key
250-
let key = first_matching_key(keyfile, &role).await?;
259+
let key = first_matching_key(keyfile, &role, match_type.as_ref()).await?;
251260

252261
// Load the invoice and sign it.
253262
let mut inv: Invoice = bindle::client::load::toml(sign_opts.invoice.as_str()).await?;
@@ -259,8 +268,8 @@ async fn run() -> std::result::Result<(), ClientError> {
259268
.unwrap_or_else(|| format!("./invoice-{}.toml", inv.canonical_name()));
260269

261270
println!(
262-
"Signed {} with role {} and wrote to {}",
263-
sign_opts.invoice, role, outfile
271+
"Signed {} with role as '{}', label as '{}' and wrote to {}",
272+
sign_opts.invoice, role, key.label, outfile
264273
);
265274
tokio::fs::write(outfile, toml::to_string(&inv)?).await?;
266275
}
@@ -315,7 +324,7 @@ async fn run() -> std::result::Result<(), ClientError> {
315324
.await
316325
.map_err(|e| ClientError::Other(e.to_string()))?;
317326

318-
let matches: Vec<KeyEntry> = match print_key_opts.label {
327+
let matches: Vec<KeyEntry> = match print_key_opts.label_matching {
319328
Some(name) => keyfile
320329
.key
321330
.iter()
@@ -646,12 +655,16 @@ async fn ensure_config_dir() -> Result<PathBuf> {
646655
Ok(dir)
647656
}
648657

649-
async fn first_matching_key(fpath: PathBuf, role: &SignatureRole) -> Result<SecretKeyEntry> {
658+
async fn first_matching_key(
659+
fpath: PathBuf,
660+
role: &SignatureRole,
661+
label_match: Option<&LabelMatch>,
662+
) -> Result<SecretKeyEntry> {
650663
let keys = SecretKeyFile::load_file(&fpath).await.map_err(|e| {
651664
ClientError::Other(format!("Error loading file {}: {}", fpath.display(), e))
652665
})?;
653666

654-
keys.get_first_matching(role)
667+
keys.get_first_matching(role, label_match)
655668
.map(|k| k.to_owned())
656669
.ok_or_else(|| ClientError::Other("No satisfactory key found".to_owned()))
657670
}

bin/client/opts.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -419,12 +419,11 @@ pub struct PrintKey {
419419
)]
420420
pub secret_file: Option<PathBuf>,
421421
#[clap(
422-
short = 'l',
423-
long = "label",
424-
value_name = "LABEL",
425-
help = "The label to search for. If supplied, this will return each key that contains this string in its label. For example, '--label=ample' will match 'label: Examples'."
422+
short = 'm',
423+
long = "label-matching",
424+
help = "selects the keys that (partially) matches the given label. If supplied, this will return each key that contains this string in its label. For example, '--label=ample' will match 'label: Examples'."
426425
)]
427-
pub label: Option<String>,
426+
pub label_matching: Option<String>,
428427
}
429428

430429
#[derive(Parser)]
@@ -447,6 +446,19 @@ pub struct SignInvoice {
447446
help = "the role to sign with. Values are: c[reator], a[pprover], h[ost], p[roxy]. If no role is specified, 'creator' is used"
448447
)]
449448
pub role: Option<String>,
449+
#[clap(
450+
short = 'l',
451+
long = "label",
452+
conflicts_with = "label-matching",
453+
help = "selects the key with the exact given label"
454+
)]
455+
pub label: Option<String>,
456+
#[clap(
457+
short = 'm',
458+
long = "label-matching",
459+
help = "selects the key that (partially) matches the given label. If supplied, this will sign with the key that contains this string in its label. For example, '--label=ample' will match 'label: Examples'."
460+
)]
461+
pub label_matching: Option<String>,
450462
#[clap(
451463
short = 'o',
452464
long = "out",

src/invoice/signature.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -365,12 +365,16 @@ impl SecretKeyEntry {
365365
/// all of them must provide a way for the system to fetch a key matching the
366366
/// desired role.
367367
pub trait SecretKeyStorage {
368-
/// Get a key appropriate for signing with the given role.
368+
/// Get a key appropriate for signing with the given role and optional match criteria with LabelMatch enum.
369369
///
370370
/// If no key is found, this will return a None.
371371
/// In general, if multiple keys match, the implementation chooses the "best fit"
372372
/// and returns that key.
373-
fn get_first_matching(&self, role: &SignatureRole) -> Option<&SecretKeyEntry>;
373+
fn get_first_matching(
374+
&self,
375+
role: &SignatureRole,
376+
label_match: Option<&LabelMatch>,
377+
) -> Option<&SecretKeyEntry>;
374378
}
375379

376380
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -414,10 +418,32 @@ impl SecretKeyFile {
414418
Ok(())
415419
}
416420
}
421+
/// This enumerates the select criteria of the key based on the given Label
422+
pub enum LabelMatch {
423+
/// Key will be selected with an exact match of the given label.
424+
/// Matches are case sensitive
425+
FullMatch(String),
426+
/// Key will be selected with a partial match of the given label,
427+
/// Matches are case insensitive.
428+
///
429+
/// For example: "pika" will match "Pikachu" and "puff" will match "Jigglypuff"
430+
PartialMatch(String),
431+
}
417432

418433
impl SecretKeyStorage for SecretKeyFile {
419-
fn get_first_matching(&self, role: &SignatureRole) -> Option<&SecretKeyEntry> {
420-
self.key.iter().find(|k| k.roles.contains(role))
434+
fn get_first_matching(
435+
&self,
436+
role: &SignatureRole,
437+
label_match: Option<&LabelMatch>,
438+
) -> Option<&SecretKeyEntry> {
439+
self.key.iter().find(|k| {
440+
k.roles.contains(role)
441+
&& match label_match {
442+
Some(LabelMatch::FullMatch(label)) => k.label.eq(label),
443+
Some(LabelMatch::PartialMatch(label)) => k.label.contains(label),
444+
None => true,
445+
}
446+
})
421447
}
422448
}
423449

src/server/handlers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ pub mod v1 {
7474
// with my private key, and THEN go on to store.create_invoice()
7575

7676
let role = SignatureRole::Host;
77-
let sk = match secret_store.get_first_matching(&role) {
77+
let sk = match secret_store.get_first_matching(&role, None) {
7878
None => {
7979
return Ok(reply::into_reply(ProviderError::FailedSigning(
8080
SignatureError::NoSuitableKey,

src/server/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ mod test {
187187
SignatureRole::Creator,
188188
valid_v1
189189
.keys
190-
.get_first_matching(&SignatureRole::Creator)
190+
.get_first_matching(&SignatureRole::Creator, None)
191191
.unwrap(),
192192
)
193193
.unwrap();

src/testing/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::invoice::signature::{
1717
use crate::provider::embedded::EmbeddedProvider;
1818
use crate::provider::file::FileProvider;
1919
use crate::search::StrictEngine;
20-
use crate::signature::KeyRingLoader;
20+
use crate::signature::{KeyRingLoader, LabelMatch};
2121

2222
use sha2::{Digest, Sha256};
2323
use tempfile::tempdir;
@@ -296,7 +296,11 @@ impl Default for MockKeyStore {
296296
}
297297

298298
impl SecretKeyStorage for MockKeyStore {
299-
fn get_first_matching(&self, _role: &SignatureRole) -> Option<&SecretKeyEntry> {
299+
fn get_first_matching(
300+
&self,
301+
_role: &SignatureRole,
302+
_match_type: Option<&LabelMatch>,
303+
) -> Option<&SecretKeyEntry> {
300304
Some(&self.mock_secret_key)
301305
}
302306
}

tests/client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ async fn test_already_created() {
170170
SignatureRole::Creator,
171171
scaffold
172172
.keys
173-
.get_first_matching(&SignatureRole::Creator)
173+
.get_first_matching(&SignatureRole::Creator, None)
174174
.unwrap(),
175175
)
176176
.unwrap();

0 commit comments

Comments
 (0)