diff --git a/pyproject.toml b/pyproject.toml index 36ad63c..8bd03e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ build-backend = "maturin" features = ["pyo3/extension-module"] python-source = "src" manifest-path = "rust/Cargo.toml" +module-name = "sigstore_tsp._rust" [tool.ruff] line-length = 100 diff --git a/rust/src/lib.rs b/rust/src/lib.rs index f3f1eea..a4f4602 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -48,7 +48,42 @@ pub struct TimeStampResp { } #[pyo3::pymethods] -impl TimeStampResp {} +impl TimeStampResp { + #[getter] + fn status(&self) -> pyo3::PyResult { + Ok(self.raw.borrow_dependent().status.status) + } + + #[getter] + fn status_string<'p>( + &self, + py: pyo3::Python<'p>, + ) -> pyo3::PyResult> { + let opt_status_strings = &self.raw.borrow_dependent().status.status_string; + match opt_status_strings { + Some(status_strings) => { + let status_list = pyo3::types::PyList::empty_bound(py); + for status_string in status_strings.clone() { + let _ = status_list + .append(pyo3::types::PyString::new_bound(py, status_string.as_str())); + } + Ok(status_list) + } + None => { + return Err(pyo3::exceptions::PyNotImplementedError::new_err( + "No status string is not yet implemented.", + )) + } + } + } + + // #[getter] + // fn fail_info( + // &self, + // ) -> pyo3::PyResult { + // Ok(self.raw.borrow_dependent().status.status) + // } +} #[pyo3::pyfunction] #[pyo3(signature = (data))] @@ -123,14 +158,25 @@ pub(crate) fn create_timestamp_request( } /// A Python module implemented in Rust. -#[pymodule] -fn sigstore_tsp(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_class::()?; - m.add_class::()?; - m.add_function(wrap_pyfunction!(parse_timestamp_response, m)?)?; - m.add_function(wrap_pyfunction!(create_timestamp_request, m)?)?; - m.add_function(wrap_pyfunction!(parse_timestamp_request, m)?)?; - Ok(()) +#[pyo3::pymodule] +mod sigstore_tsp { + + use super::*; + + #[pyo3::pymodule] + mod _rust { + + use super::*; + + #[pymodule_export] + use super::parse_timestamp_response; + + #[pymodule_export] + use super::create_timestamp_request; + + #[pymodule_export] + use super::parse_timestamp_request; + } } #[cfg(test)] diff --git a/rust/src/tsp.rs b/rust/src/tsp.rs index 08d1566..68adc76 100644 --- a/rust/src/tsp.rs +++ b/rust/src/tsp.rs @@ -59,9 +59,9 @@ pub struct RawTimeStampReq<'a> { // failInfo PKIFailureInfo OPTIONAL } #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub(crate) struct PKIStatusInfo<'a> { - status: u8, - status_string: Option>>, - fail_info: Option>, + pub status: u8, + pub status_string: Option>>, + pub fail_info: Option>, } // Accuracy ::= SEQUENCE { diff --git a/src/sigstore_tsp/_rust/__init__.pyi b/src/sigstore_tsp/_rust/__init__.pyi new file mode 100644 index 0000000..17b868f --- /dev/null +++ b/src/sigstore_tsp/_rust/__init__.pyi @@ -0,0 +1,12 @@ +class TimeStampRequest: ... + +def create_timestamp_request( + data: bytes, +) -> TimeStampRequest: ... + + +class TimeStampResponse: ... + +def parse_timestamp_response( + data: bytes, +) -> TimeStampResponse: ... diff --git a/src/sigstore_tsp/base.py b/src/sigstore_tsp/base.py index de41dce..8916534 100644 --- a/src/sigstore_tsp/base.py +++ b/src/sigstore_tsp/base.py @@ -1,26 +1,98 @@ +"""Base implementation.""" + from __future__ import annotations +import enum + +from sigstore_tsp import _rust + + +class HashAlgorithm(enum.Enum): + """Hash algorithms.""" + + SHA512 = "SHA512" + + +_AllowedHashTypes = HashAlgorithm + class TimestampRequestBuilder: - def __init__(self, data: bytes| None = None, hash_algorithm: None = None, req_policy: None = None, nonce: int | None = None, cert_req : bool = False, extensions: None = None): - self._data = data - self._algorithm = hash_algorithm + """Timestamp Request Builder class.""" + + def __init__( + self, + data: bytes | None = None, + hash_algorithm: _AllowedHashTypes | None = None, + req_policy: None | int = None, + cert_req: bool = False, # noqa: FBT001, FBT002 + extensions: None = None, + ) -> None: + """Init method.""" + self._data: bytes | None = data + self._algorithm: _AllowedHashTypes | None = hash_algorithm self._req_policy = req_policy - self._nonce = nonce - self._cert_req = cert_req + self._cert_req: bool = cert_req self._extensions = extensions def data(self, data: bytes) -> TimestampRequestBuilder: + """Set the data to be timestamped.""" if not data: - raise ValueError("The data to timestamp cannot be empty.") + msg = "The data to timestamp cannot be empty." + raise ValueError(msg) if self._data is not None: - raise ValueError("The data may only be set once.") + msg = "The data may only be set once." + raise ValueError(msg) return TimestampRequestBuilder( - data, self._algorithm, self._req_policy, self._nonce, self._cert_req, self._extensions + data, self._algorithm, self._req_policy, self._cert_req, self._extensions ) - def add_extension(self, extension: None) -> TimestampRequestBuilder: - raise NotImplemented("Adding extensions is not yet supported.") + def add_extension(self, _extension: None) -> TimestampRequestBuilder: + """Add an extension.""" + msg = "Adding extensions is not yet supported." + raise NotImplementedError(msg) + + def hash_algorithm(self, hash_algorihtm: _AllowedHashTypes) -> TimestampRequestBuilder: + """Set the Hash algorithm used.""" + if hash_algorihtm not in HashAlgorithm: + msg = f"{hash_algorihtm} is not a supported hash." + raise TypeError(msg) + + return TimestampRequestBuilder( + self._data, hash_algorihtm, self._req_policy, self._cert_req, self._extensions + ) + + def cert_request(self, *, cert_request: bool = False) -> TimestampRequestBuilder: + """Set the cert request field.""" + if not isinstance(cert_request, bool): + msg = "Cert request must be a boolean." + raise TypeError(msg) + + return TimestampRequestBuilder( + self._data, self._algorithm, self._req_policy, cert_request, self._extensions + ) + + def request_policy(self, request_policy: int) -> TimestampRequestBuilder: + """Set the request policy field.""" + if not isinstance(request_policy, int): + msg = "Request policy must be an integer." + raise TypeError(msg) + + return TimestampRequestBuilder( + self._data, self._algorithm, request_policy, self._cert_req, self._extensions + ) + + def build(self) -> _rust.TimeStampRequest: + """Build a TimestampRequest.""" + if self._data is None: + msg = "Data must be for a Timestamp Request." + raise ValueError(msg) + + if self._algorithm is None: + self._algorithm = HashAlgorithm.SHA512 + + return _rust.create_timestamp_request(self._data) + - def build(self): - raise NotImplemented("Building is not yet supported.") \ No newline at end of file +def decode_timestamp_response(data: bytes) -> _rust.TimeStampResponse: + """Decode a Timestamp response.""" + return _rust.parse_timestamp_response(data) diff --git a/src/sigstore_tsp/bindings/__init__.py b/src/sigstore_tsp/bindings/__init__.py deleted file mode 100644 index e69de29..0000000