Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 85 additions & 11 deletions Cargo.lock

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

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ hex = "0.4.3"
once_cell = "1.21"
testresult = "0.4"
sequoia-openpgp = { version = "2", default-features = false, features = [ "crypto-rust", "allow-experimental-crypto", "allow-variable-time-crypto"] }
pyo3-introspection = "0.26.0"

[dependencies.pyo3]
version = "0.25"
version = "0.26"
# "py-clone" feature added to keep the original behavior but it'd be good to avoid it
# see: https://github.com/PyO3/pyo3/pull/4095
features = ["extension-module", "anyhow", "chrono", "py-clone"]
features = ["extension-module", "anyhow", "chrono", "py-clone", "experimental-inspect"]
16 changes: 16 additions & 0 deletions code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import pysequoia
import subprocess


# the types can go in quotes, only MyPy or humans really care
def sign(data: bytes, signer: "pysequoia.PySigner") -> bytes:
return pysequoia.sign(signer, data)


# this is my key-id, will want to use one's own
data = subprocess.check_output(
["sq", "key", "export", "--cert", "9D5A2BD5688ECB889DEBCD3FC2602803128069A7"]
)
cert = pysequoia.Cert.from_bytes(data)
result = sign(b"attack at dawn", cert.secrets.signer("passphrase"))
print(result)
79 changes: 79 additions & 0 deletions pysequoia.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import typing

class Cert:
def __bytes__(self, /) -> typing.Any: ...
def __repr__(self, /) -> str: ...
def __str__(self, /) -> str: ...
def add_user_id(self, /, value: str, certifier: PySigner) -> Cert: ...
@property
def expiration(self, /) -> typing.Any: ...
@property
def fingerprint(self, /) -> str: ...
@staticmethod
def from_bytes(bytes: typing.Any) -> Cert: ...
@staticmethod
def from_file(path: str) -> Cert: ...
@staticmethod
def generate(user_id: str | None = None, user_ids: typing.Any | None = None, profile: Profile | None = None) -> Cert: ...
@property
def has_secret_keys(self, /) -> bool: ...
@property
def is_revoked(self, /) -> bool: ...
def merge(self, /, new_cert: Cert) -> Cert: ...
def revoke(self, /, certifier: PySigner) -> Sig: ...
def revoke_user_id(self, /, user_id: UserId, certifier: PySigner) -> Sig: ...
@property
def secrets(self, /) -> typing.Any: ...
def set_expiration(self, /, expiration: typing.Any, certifier: PySigner) -> Cert: ...
def set_notations(self, /, certifier: PySigner, notations: typing.Any) -> Cert: ...
@staticmethod
def split_bytes(bytes: typing.Any) -> typing.Any: ...
@staticmethod
def split_file(path: str) -> typing.Any: ...
@property
def user_ids(self, /) -> typing.Any: ...

class Decrypted:
@property
def bytes(self, /) -> typing.Any: ...
@property
def valid_sigs(self, /) -> typing.Any: ...

class Notation:
def __new__(cls, /, key: str, value: str) -> None: ...
def __repr__(self, /) -> str: ...
def __str__(self, /) -> str: ...
@property
def key(self, /) -> str: ...
@property
def value(self, /) -> str: ...

class Profile: ...
class PyDecryptor: ...
class PySigner: ...

class Sig:
def __bytes__(self, /) -> typing.Any: ...
def __repr__(self, /) -> str: ...
def __str__(self, /) -> str: ...
@property
def created(self, /) -> typing.Any: ...
@staticmethod
def from_bytes(bytes: typing.Any) -> Sig: ...
@staticmethod
def from_file(path: str) -> Sig: ...
@property
def issuer_fpr(self, /) -> typing.Any: ...

class SignatureMode: ...

class UserId:
def __repr__(self, /) -> str: ...
def __str__(self, /) -> str: ...
@property
def notations(self, /) -> typing.Any: ...

def decrypt(decryptor: PyDecryptor, bytes: typing.Any, store: typing.Any | None = None) -> Decrypted: ...
def encrypt(recipients: typing.Any, bytes: typing.Any, signer: PySigner | None = None) -> typing.Any: ...
def sign(signer: PySigner, bytes: typing.Any, *, mode: SignatureMode = ...) -> typing.Any: ...
def verify(bytes: typing.Any | None = None, store: typing.Any | None = None, file: typing.Any | None = None, signature: Sig | None = None) -> Decrypted: ...
38 changes: 27 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,31 @@ impl Decrypted {
}

#[pymodule]
fn pysequoia(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<cert::Cert>()?;
m.add_class::<cert::Profile>()?;
m.add_class::<signature::Sig>()?;
m.add_class::<notation::Notation>()?;
m.add_class::<sign::SignatureMode>()?;
m.add_function(wrap_pyfunction!(sign::sign, m)?)?;
m.add_function(wrap_pyfunction!(encrypt::encrypt, m)?)?;
m.add_function(wrap_pyfunction!(decrypt::decrypt, m)?)?;
m.add_function(wrap_pyfunction!(verify::verify, m)?)?;
Ok(())
pub mod pysequoia {
#[pymodule_export]
pub use super::cert::Cert;
#[pymodule_export]
pub use super::cert::Profile;
#[pymodule_export]
pub use super::decrypt::decrypt;
#[pymodule_export]
pub use super::decrypt::PyDecryptor;
#[pymodule_export]
pub use super::encrypt::encrypt;
#[pymodule_export]
pub use super::notation::Notation;
#[pymodule_export]
pub use super::sign::sign;
#[pymodule_export]
pub use super::sign::SignatureMode;
#[pymodule_export]
pub use super::signature::Sig;
#[pymodule_export]
pub use super::signer::PySigner;
#[pymodule_export]
pub use super::user_id::UserId;
#[pymodule_export]
pub use super::verify::verify;
#[pymodule_export]
pub use super::Decrypted;
}
14 changes: 14 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use std::path::PathBuf;

fn main() {
println!("test");
let module = pyo3_introspection::introspect_cdylib(
"./.env/lib/python3.13/site-packages/pysequoia/pysequoia.cpython-313-x86_64-linux-gnu.so",
"pysequoia",
)
.expect("introspection to succeed");
let result = pyo3_introspection::module_stub_files(&module);
println!("{result:?}");
let value = result.get(&PathBuf::from("__init__.pyi")).unwrap();
std::fs::write("pysequoia.pyi", value).unwrap();
}
2 changes: 1 addition & 1 deletion src/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use sequoia_openpgp::serialize::stream::{LiteralWriter, Message};

use crate::signer::PySigner;

#[pyclass(eq, eq_int)]
#[pyclass]
#[derive(PartialEq)]
pub enum SignatureMode {
#[pyo3(name = "INLINE")]
Expand Down
Loading