Skip to content

Commit

Permalink
Add support for parsing detached signatures
Browse files Browse the repository at this point in the history
Fixes: #27
Signed-off-by: Wiktor Kwapisiewicz <[email protected]>
  • Loading branch information
wiktor-k committed Apr 19, 2024
1 parent af49c56 commit fbf12f3
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 143 deletions.
234 changes: 120 additions & 114 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ hex = "0.4.3"
once_cell = "1.19"
openpgp-card = "0.4.2"
openpgp-card-sequoia = { version = "0.2.1", default-features = false }
pyo3 = { version = "0.20", features = ["extension-module", "anyhow", "chrono"] }
sequoia-openpgp = { version = "1.19", default-features = false, features = ["compression"] }
pyo3 = { version = "0.21", features = ["extension-module", "anyhow", "chrono"] }
sequoia-openpgp = { version = "1.20", default-features = false, features = ["compression"] }
testresult = "0.4"

[target.'cfg(target_os = "linux")'.dependencies]
Expand Down
14 changes: 6 additions & 8 deletions NEXT.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
# Next version changes
## This file contains changes that will be included in the next version that is released
v0.1.23
v0.1.24

New:
- `decrypt` accepts a function for supplying certificates for signature verification ([#22])
- the result of `decrypt` and `verify` exposes `valid_sigs` for retrieving a list of valid signatures ([#22])
- `Sig` - new class exposing signature related functions:
- `Sig.from_file` - read detached signature from file,
- `Sig.from_bytes` - read detached signature from bytes,
- `sig.issuer_fpr` - fingerprint of the issuer (may be `None`),
- `sig.created` - date and time when the signature was issued,

Changed:
- `verify` accepts a callback for supplying signing certificates ([#20])
- `encrypt` does not require the `signer` argument ([#22])

Removed:
- `Store` and the Cert-D has been removed ([#20]) due to confusing semantics

[#20]: https://github.com/wiktor-k/pysequoia/pull/20
[#22]: https://github.com/wiktor-k/pysequoia/pull/22
### git tag --edit -s -F NEXT.md v...
24 changes: 15 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,21 @@ private_parts = Cert.from_bytes(f"{c.secrets}".encode("utf8"))
assert private_parts.has_secret_keys
```

## Signatures

Detached signatures can be read directly from files (`Sig.from_file`) or bytes in memory (`Sig.from_bytes`):

```python
from pysequoia import Sig

sig = Sig.from_file("sig.pgp")

print(f"Parsed signature: {repr(sig)}")

assert sig.issuer_fpr == "e8f23996f23218640cb44cbe75cf5ac418b8e74c"
assert sig.created == datetime.fromisoformat("2023-07-19T18:14:01+00:00")
```

## OpenPGP Cards

There's an experimental feature allowing communication with OpenPGP
Expand Down Expand Up @@ -514,12 +529,3 @@ Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in the package by you shall be under the terms
and conditions of this license, without any additional terms or
conditions.

## Sponsors

My work was supported by these generous organizations (alphabetical
order):

- [nlnet.nl](https://nlnet.nl/)
- [pep.foundation](https://pep.foundation/)
- [sovereigntechfund.de](https://sovereigntechfund.de/en.html)
Binary file added sig.pgp
Binary file not shown.
8 changes: 4 additions & 4 deletions src/cert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ impl Cert {
}

#[staticmethod]
pub fn generate(user_id: Option<&str>, user_ids: Option<Vec<&str>>) -> PyResult<Self> {
pub fn generate(user_id: Option<&str>, user_ids: Option<Vec<String>>) -> PyResult<Self> {
use openpgp::types::KeyFlags;
let mut builder = CertBuilder::new()
.set_cipher_suite(CipherSuite::default())
Expand Down Expand Up @@ -144,7 +144,7 @@ impl Cert {
&mut self,
user_id: &UserId,
mut certifier: PySigner,
) -> PyResult<crate::signature::Signature> {
) -> PyResult<crate::signature::Sig> {
let userid = UserID::from(user_id.__str__());
let builder = signature::SignatureBuilder::new(SignatureType::CertificationRevocation);
Ok(userid.bind(&mut certifier, &self.cert, builder)?.into())
Expand Down Expand Up @@ -246,13 +246,13 @@ impl Cert {
Ok(self.cert.to_vec()?.into())
}

pub fn revoke(&self, mut certifier: PySigner) -> PyResult<crate::signature::Signature> {
pub fn revoke(&self, mut certifier: PySigner) -> PyResult<crate::signature::Sig> {
let signature = self.cert.revoke(
&mut certifier,
openpgp::types::ReasonForRevocation::Unspecified,
&[],
)?;
Ok(crate::signature::Signature::new(signature))
Ok(crate::signature::Sig::new(signature))
}

#[getter]
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,10 @@ impl Decrypted {
}

#[pymodule]
fn pysequoia(_py: Python, m: &PyModule) -> PyResult<()> {
fn pysequoia(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<cert::Cert>()?;
m.add_class::<card::Card>()?;
m.add_class::<signature::Sig>()?;
m.add_class::<notation::Notation>()?;
m.add_function(wrap_pyfunction!(sign::sign, m)?)?;
m.add_function(wrap_pyfunction!(encrypt::encrypt, m)?)?;
Expand Down
62 changes: 57 additions & 5 deletions src/signature.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,86 @@
use std::borrow::Cow;

use openpgp::packet::Signature as SqSignature;
use anyhow::anyhow;
use openpgp::{
packet::Signature as SqSignature,
parse::{PacketParser, PacketParserResult, Parse as _},
Packet,
};
use pyo3::prelude::*;
use sequoia_openpgp as openpgp;

#[pyclass]
pub struct Signature {
pub struct Sig {
sig: SqSignature,
}

impl Signature {
impl Sig {
pub fn new(sig: SqSignature) -> Self {
Self { sig }
}

pub fn from_packets(ppr: PacketParserResult<'_>) -> Result<Self, anyhow::Error> {
if let PacketParserResult::Some(pp) = ppr {
let (packet, _next_ppr) = pp.recurse()?;
if let Packet::Signature(sig) = packet {
return Ok(sig.into());
}
}
Err(anyhow!("Not a signature"))
}
}

impl From<SqSignature> for Signature {
impl From<SqSignature> for Sig {
fn from(sig: SqSignature) -> Self {
Self { sig }
}
}

#[pymethods]
impl Signature {
impl Sig {
#[staticmethod]
pub fn from_file(path: String) -> PyResult<Self> {
Ok(Self::from_packets(PacketParser::from_file(path)?)?)
}

#[staticmethod]
pub fn from_bytes(bytes: &[u8]) -> PyResult<Self> {
Ok(Self::from_packets(PacketParser::from_bytes(bytes)?)?)
}

pub fn bytes(&self) -> PyResult<Cow<[u8]>> {
Ok(crate::serialize(self.sig.clone().into(), None)?.into())
}

#[getter]
pub fn issuer_fpr(&self) -> Option<String> {
self.sig
.issuer_fingerprints()
.next()
.map(|issuer| format!("{:x}", issuer))
}

#[getter]
pub fn created(&self) -> Option<chrono::DateTime<chrono::Utc>> {
self.sig.signature_creation_time().map(Into::into)
}

pub fn __str__(&self) -> PyResult<String> {
let bytes = crate::serialize(self.sig.clone().into(), openpgp::armor::Kind::Signature)?;
Ok(String::from_utf8(bytes)?)
}

pub fn __repr__(&self) -> String {
format!("<Sig issuer_fpr={}>", self.issuer_fpr().unwrap_or_default())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_reading_sig() {
Sig::from_packets(PacketParser::from_file("sig.pgp").unwrap()).unwrap();
}
}

0 comments on commit fbf12f3

Please sign in to comment.