Skip to content

Commit

Permalink
Merge pull request #9 from wiktor-k/more-card-features
Browse files Browse the repository at this point in the history
Add more card features
  • Loading branch information
wiktor-k authored Nov 23, 2023
2 parents b60a5f1 + 0f2e46a commit bba0180
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 5 deletions.
1 change: 1 addition & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github: wiktor-k
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ crate-type = ["cdylib"]
anyhow = "1"
card-backend-pcsc = "0.5"
chrono = "0.4"
hex = "0.4.3"
once_cell = "1.18"
openpgp-card = "0.4.0"
openpgp-card-sequoia = { version = "0.2.0", default-features = false }
openpgp-cert-d = "0.1"
pyo3 = { version = "0.20", features = ["extension-module", "anyhow", "chrono"] }
Expand Down
4 changes: 3 additions & 1 deletion NEXT.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Next version changes
## This file contains changes that will be included in the next version that is released
v0.2.0
v0.1.21

New:
- `Card.cert_url` - for retrieving certificate URL stored on the card, note that the URL returned can be empty or invalid,
- `Card.keys` - for enumerating secret keys stored on the card,

Changed:

Expand Down
35 changes: 32 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,13 @@ gpg --batch --pinentry-mode loopback --passphrase '' --export-secret-key no-pass
# initialize dummy OpenPGP Card
sh /start.sh
echo 12345678 > pin
opgpcard admin --card 0000:00000000 --admin-pin pin import no-passwd.pgp
CARD_ADMIN="opgpcard admin --card 0000:00000000 --admin-pin pin"
$CARD_ADMIN import full-key.asc
$CARD_ADMIN name "John Doe"
$CARD_ADMIN url "https://example.com/key.pgp"
$CARD_ADMIN touch --key SIG --policy Fixed
$CARD_ADMIN touch --key DEC --policy Off
$CARD_ADMIN touch --key AUT --policy Fixed
```

## Functions
Expand Down Expand Up @@ -406,9 +412,32 @@ all = Card.all()
card = Card.open("0000:00000000")

print(f"Card ident: {card.ident}")
print(f"Cardholder: {card.cardholder}")
assert card.cardholder == "John Doe"
assert card.cert_url == "https://example.com/key.pgp"
```

Cards provide `keys` property that can be used to see which keys are imported
on the card:

```python
keys = card.keys
print(f"Keys: {keys}")
assert len(keys) == 3

assert keys[0].fingerprint == "ddc3e03c91fb52ca2d95c2444566f2743ed5f382"
assert "sign" in keys[0].usage
assert keys[0].touch_required

assert keys[1].fingerprint == "689e152a7420be13dcaf2c142ac27adc1db9395e"
assert "decrypt" in keys[1].usage
assert not keys[1].touch_required

assert keys[2].fingerprint == "731fbca93ce9821347bf8e696444723371d3c650"
assert "authenticate" in keys[2].usage
assert keys[2].touch_required
```


Cards can be used for signing data:

```python
Expand All @@ -424,7 +453,7 @@ As well as for decryption:
decryptor = card.decryptor("123456")

sender = Cert.from_file("passwd.pgp")
receiver = Cert.from_file("no-passwd.pgp")
receiver = Cert.from_file("full-key.asc")

content = "Red Green Blue"

Expand Down
34 changes: 34 additions & 0 deletions full-key.asc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
Comment: F6C0 B7D0 BE82 025B EFFA 5EE0 4D26 BE97 42B6 BBC7

xVgEZV8mnBYJKwYBBAHaRw8BAQdAJDEsAEHqAegSu4T1NhLE7g2S2HHy4grzbpwq
9AiLw+UAAQC3vMRsRHXFH1UasVFlaJKuMi5vnh/6zZIC+HKTzveufhCUwsARBB8W
CgCDBYJlXyacBYkFpI+9AwsJBwkQTSa+l0K2u8dHFAAAAAAAHgAgc2FsdEBub3Rh
dGlvbnMuc2VxdW9pYS1wZ3Aub3JnmXA62AXv9IgC1jf0lrW3qDxOnM3L4FyOCZP0
T/QcX2oDFQoIApsBAh4BFiEE9sC30L6CAlvv+l7gTSa+l0K2u8cAAC9sAQDF3O3V
F7Io3q4ooV5T9c4wU3k1qgX6L/La9J7YZbAFNgD/XD0DZH591o7npestwbEzy6tP
41lXOfmmem4g1u1imQvHWARlXyacFgkrBgEEAdpHDwEBB0A0t/rqqLhO3H6pGnev
2RVJaHWX+YaO1BDhYpwfzM6fuAAA/joDnLjfZvKrsOnM5UPgBJ6ezamFvGlObtRD
0mRlVOxoE1TCwMUEGBYKATcFgmVfJpwFiQWkj70JEE0mvpdCtrvHRxQAAAAAAB4A
IHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ8z+3bumoO/kE4EwsErcwzw3
826BSouBjm7uu/5ehvotApsCvqAEGRYKAG8FgmVfJpwJEEVm8nQ+1fOCRxQAAAAA
AB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZw7po5+COFy4etWJg2nm
+i/9PScl422lMcBzCR44aPOVFiEE3cPgPJH7UsotlcJERWbydD7V84IAAJfLAQDs
THGJXK7Endv4V6115rrkywEv39V6OwfmpmlJKrG5zAEAi929loAzFS6z6OSDx295
SG0hupFJ6UVOwOVXFd+26QcWIQT2wLfQvoICW+/6XuBNJr6XQra7xwAAoCIBAOtF
eJaKP7JzSKiZRBSu+xPSvTjiTzMdkZaLRRPF/6saAP9PxVbzs08y8qMLaSUrpfkg
O3jPDVES5MdglikhK7PUD8dYBGVfJpwWCSsGAQQB2kcPAQEHQJDciggHlBrtgrZd
lXDBDHsgSQTwpKPNuEXDZqIrDQvXAAEA0wdknEP9as31ddUvzcjmnt91W5SM5OC7
52v3fbiCAqoT08LABgQYFgoAeAWCZV8mnAWJBaSPvQkQTSa+l0K2u8dHFAAAAAAA
HgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnrKpsjxP3qa5eaieh9pHi
A3A++ziK7MsUvqHPYy4oSKUCmyAWIQT2wLfQvoICW+/6XuBNJr6XQra7xwAAZiEB
AJJQ0sQryZmb0fld/hC8QG22QFn4Ikn9Vvo7B2TocQ6UAQDEt+cz0NQtNU6Wl6qY
7AwF3T/0UJHhWZyZd7DSfxkTA8ddBGVfJpwSCisGAQQBl1UBBQEBB0CPSesAmVD/
J2GV/YbGB/B3tsLAZH9knrUYs8Zykq81AQMBCAcAAP9qKRK1yDqk28GM/G7gvm5B
qGRBydJqwUjrF3Gjn6m2ABJNwsAGBBgWCgB4BYJlXyacBYkFpI+9CRBNJr6XQra7
x0cUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfuHcLDGf/3
uJvZemz5fmZNhTCZ8VndC8uhuBv4J/jBDgKbDBYhBPbAt9C+ggJb7/pe4E0mvpdC
trvHAABEcAD/Rp6y0YAk69SatdaTG/DgjCEzOJRyYL/S52XAWK0wNr8A/AhtbkuL
BPxLEbJ4QkSsjJROF+HC08GXlI+XcLE3t2wI
=ZfI0
-----END PGP PRIVATE KEY BLOCK-----
79 changes: 78 additions & 1 deletion src/card.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use card_backend_pcsc::PcscBackend;
use openpgp_card_sequoia::state::Open;
use openpgp_card_sequoia::types::{Fingerprint, KeyType};
use openpgp_card_sequoia::Card as CCard;
use pyo3::prelude::*;
use sequoia_openpgp as openpgp;
Expand Down Expand Up @@ -31,6 +32,12 @@ impl Card {
.map(|name| String::from_utf8_lossy(name).into()))
}

#[getter]
pub fn cert_url(&mut self) -> anyhow::Result<String> {
let mut transaction = self.open.transaction()?;
Ok(transaction.url()?)
}

#[getter]
pub fn ident(&mut self) -> anyhow::Result<String> {
let transaction = self.open.transaction()?;
Expand Down Expand Up @@ -170,6 +177,76 @@ impl Card {
}

pub fn __repr__(&mut self) -> anyhow::Result<String> {
Ok(format!("<Card ident={}>", self.ident()?))
Ok(format!(
"<Card ident={} cardholder='{:?}' cert_url='{}'>",
self.ident()?,
self.cardholder()?,
self.cert_url()?
))
}

#[getter]
pub fn keys(&mut self) -> anyhow::Result<Vec<CardKey>> {
let transaction = self.open.transaction()?;
let card_keys = transaction.fingerprints()?;
let mut keys = Vec::with_capacity(3);
let mut append_key = |key: Option<&Fingerprint>, key_type: KeyType| {
if let Some(key) = key {
let usage = match key_type {
KeyType::Signing => Some("sign"),
KeyType::Decryption => Some("decrypt"),
KeyType::Authentication => Some("authenticate"),
_ => None,
}
.map(Into::into)
.into_iter()
.collect();
keys.push(CardKey {
fingerprint: hex::encode(key.as_bytes()),
usage,
touch_required: transaction
.user_interaction_flag(key_type)
.unwrap_or_default()
.map(|uif| uif.touch_policy().touch_required())
.unwrap_or_default(),
})
}
};
append_key(card_keys.signature(), KeyType::Signing);
append_key(card_keys.decryption(), KeyType::Decryption);
append_key(card_keys.authentication(), KeyType::Authentication);
Ok(keys)
}
}

#[pyclass]
pub struct CardKey {
fingerprint: String,
usage: Vec<String>,
touch_required: bool,
}

#[pymethods]
impl CardKey {
#[getter]
fn fingerprint(&self) -> &String {
&self.fingerprint
}

#[getter]
fn usage(&self) -> Vec<String> {
self.usage.clone()
}

#[getter]
fn touch_required(&self) -> bool {
self.touch_required
}

pub fn __repr__(&self) -> String {
format!(
"<Key fingerprint={} usage={:?} touch_required={}>",
self.fingerprint, self.usage, self.touch_required
)
}
}

0 comments on commit bba0180

Please sign in to comment.