diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ec37785..9d802f9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,8 +15,6 @@ jobs: - uses: actions/setup-python@v5 with: python-version-file: pyproject.toml - cache: "pip" - cache-dependency-path: pyproject.toml - name: Setup rust uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -24,12 +22,12 @@ jobs: components: rustfmt toolchain: 1.81.0 - - name: install uv - run: > - curl --no-progress-meter --location --fail - --proto '=https' --tlsv1.2 - "https://astral.sh/uv/install.sh" - | sh + - name: Install the latest version of uv + uses: astral-sh/setup-uv@v3 + with: + version: "0.4.18" + enable-cache: true + cache-dependency-glob: pyproject.toml - name: lint run: make lint INSTALL_EXTRA=lint diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ac70bbd..c276fad 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,23 +11,24 @@ jobs: strategy: matrix: python: + - "3.9" + - "3.10" + - "3.11" - "3.12" + - "3.13" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - name: Install the latest version of uv + uses: astral-sh/setup-uv@v3 with: - python-version: ${{ matrix.python }} - cache: "pip" - cache-dependency-path: pyproject.toml + version: "0.4.18" + enable-cache: true + cache-dependency-glob: pyproject.toml - - name: install uv - run: > - curl --no-progress-meter --location --fail - --proto '=https' --tlsv1.2 - "https://astral.sh/uv/install.sh" - | sh + - name: Install Python ${{ matrix.python }} + run: uv python install ${{ matrix.python }} - name: Setup rust uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 diff --git a/Makefile b/Makefile index 16a9788..4e8f854 100644 --- a/Makefile +++ b/Makefile @@ -74,3 +74,4 @@ test tests: $(VENV)/pyvenv.cfg pytest --cov=$(PY_IMPORT) $(T) $(TEST_ARGS) && \ python -m coverage report -m $(COV_ARGS) cargo test --manifest-path rust/Cargo.toml + cargo test --manifest-path rust/tsp-asn1/Cargo.toml diff --git a/pyproject.toml b/pyproject.toml index 03e6927..085f3d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,20 +11,13 @@ classifiers = [ dynamic = ["version"] readme = "README.md" license = { file = "LICENSE" } -authors = [ - { name = "Trail of Bits", email = "opensource@trailofbits.com" }, -] -dependencies = [ - "maturin>=1.7,<2.0", - "cryptography" -] +authors = [{ name = "Trail of Bits", email = "opensource@trailofbits.com" }] +dependencies = ["maturin>=1.7,<2.0", "cryptography"] [project.optional-dependencies] doc = [] test = ["pytest", "pytest-cov", "pretend", "coverage[toml]"] -lint = [ - "ruff ~= 0.6.7" -] +lint = ["ruff ~= 0.6.7"] dev = ["sigstore-tsp[test,lint,doc]"] [project.urls] @@ -48,8 +41,4 @@ line-length = 100 include = ["src/**/*.py"] [tool.ruff.lint] -select = ["ALL"] -# D203 and D213 are incompatible with D211 and D212 respectively. -# COM812 and ISC001 can cause conflicts when using ruff as a formatter. -# See https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules. -ignore = ["D203", "D213", "COM812", "ISC001"] \ No newline at end of file +select = ["E", "F", "I", "W", "UP", "TCH"] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 0fd7d71..1363e12 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -3,8 +3,9 @@ name = "sigstore-tsp" version = "0.1.0" edition = "2021" authors = [ - "Alexis Challande " + "Trail of Bits " ] +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 25e9683..80e6870 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,18 +1,18 @@ pub mod oid; pub mod util; -use std::sync::Arc; - +use asn1::SimpleAsn1Readable; use pyo3::{exceptions::PyValueError, prelude::*}; use rand::Rng; use sha2::Digest; use tsp_asn1::cms::SignedData as RawSignedData; use tsp_asn1::tsp::{ MessageImprint as RawMessageImprint, RawTimeStampReq, RawTimeStampResp, TSTInfo as RawTSTInfo, + TimeStampToken, }; self_cell::self_cell!( - struct OwnedTimeStamReq { + struct OwnedTimeStampReq { owner: pyo3::Py, #[covariant] dependent: RawTimeStampReq, @@ -21,7 +21,7 @@ self_cell::self_cell!( #[pyo3::pyclass] pub struct TimeStampReq { - raw: Arc, + raw: OwnedTimeStampReq, } #[pyo3::pymethods] @@ -56,13 +56,12 @@ impl TimeStampReq { } #[getter] - fn message_imprint(&self) -> PyResult { - // fn message_imprint<'p>(&self, py: pyo3::Python<'p>) -> PyResult { + fn message_imprint(&self, py: pyo3::Python<'_>) -> PyResult { Ok(PyMessageImprint { - contents: OwnedMessageImprint::try_new(Arc::clone(&self.raw), |v| { - Ok::<_, ()>(v.borrow_dependent().message_imprint.clone()) + contents: OwnedMessageImprint::try_new(self.raw.borrow_owner().clone_ref(py), |v| { + RawMessageImprint::parse_data(v.as_bytes(py)) }) - .unwrap(), + .map_err(|_| PyValueError::new_err("invalid message imprint"))?, }) } @@ -80,8 +79,7 @@ impl TimeStampReq { self_cell::self_cell!( struct OwnedMessageImprint { - owner: Arc, - // owner: pyo3::Py, + owner: pyo3::Py, #[covariant] dependent: RawMessageImprint, } @@ -114,7 +112,7 @@ impl PyMessageImprint { } self_cell::self_cell!( - struct OwnedTimeStamResp { + struct OwnedTimeStampResp { owner: pyo3::Py, #[covariant] dependent: RawTimeStampResp, @@ -123,7 +121,7 @@ self_cell::self_cell!( #[pyo3::pyclass] pub struct TimeStampResp { - raw: Arc, + raw: OwnedTimeStampResp, } #[pyo3::pymethods] @@ -158,18 +156,22 @@ impl TimeStampResp { // TST INFO #[getter] - fn tst_info(&self) -> PyResult { + fn tst_info(&self, py: pyo3::Python<'_>) -> PyResult { let py_tstinfo = PyTSTInfo { - raw: OwnedTSTInfo::try_new(Arc::clone(&self.raw), |v| { - let timestamp_token = v.borrow_dependent().time_stamp_token.as_ref(); - match timestamp_token { - Some(content) => match &content.content { - tsp_asn1::tsp::Content::SignedData(signed_data) => { - let tst_info = signed_data.as_inner().content_info.tst_info().unwrap(); - Ok::<_, PyErr>(tst_info) - } - }, - None => Err(pyo3::exceptions::PyValueError::new_err("")), + raw: OwnedTSTInfo::try_new(self.raw.borrow_owner().clone_ref(py), |v| { + let resp = RawTimeStampResp::parse_data(v.as_bytes(py)) + .map_err(|_| PyValueError::new_err("invalid TimeStampResp"))?; + + match resp.time_stamp_token { + Some(TimeStampToken { + _content_type, + content: tsp_asn1::tsp::Content::SignedData(signed_data), + }) => signed_data + .as_inner() + .content_info + .tst_info() + .map_err(|_| PyValueError::new_err("invalid TSTInfo")), + None => Err(PyValueError::new_err("missing TimeStampToken")), } }) .unwrap(), @@ -178,27 +180,18 @@ impl TimeStampResp { } // Signed Data - fn signed_data(&self) -> PyResult { + fn signed_data(&self, py: pyo3::Python<'_>) -> PyResult { let py_signed_data = SignedData { - raw: OwnedSignedData::try_new(Arc::clone(&self.raw), |v| { - let timestamp_token = v.borrow_dependent().time_stamp_token.as_ref(); - match timestamp_token { - Some(content) => match &content.content { - tsp_asn1::tsp::Content::SignedData(signed_data) => { - let s = signed_data.as_inner(); - Ok::<_, PyErr>(RawSignedData { - version: s.version, - digest_algorithms: s.digest_algorithms.clone(), - content_info: s.content_info.clone(), - certificates: s.certificates.clone(), - crls: None, - signer_infos: s.signer_infos.clone(), - }) - } - }, - None => Err(pyo3::exceptions::PyValueError::new_err( - "Missing Timestamp Content", - )), + raw: OwnedSignedData::try_new(self.raw.borrow_owner().clone_ref(py), |v| { + let resp = RawTimeStampResp::parse_data(v.as_bytes(py)) + .map_err(|_| PyValueError::new_err("invalid TimeStampResp"))?; + + match resp.time_stamp_token { + Some(TimeStampToken { + _content_type, + content: tsp_asn1::tsp::Content::SignedData(signed_data), + }) => Ok(*signed_data.into_inner()), + None => Err(PyValueError::new_err("missing TimeStampToken")), } }) .unwrap(), @@ -209,7 +202,7 @@ impl TimeStampResp { self_cell::self_cell!( pub struct OwnedSignedData { - owner: Arc, + owner: pyo3::Py, #[covariant] dependent: RawSignedData, } @@ -273,7 +266,7 @@ impl SignedData { self_cell::self_cell!( pub struct OwnedTSTInfo { - owner: Arc, + owner: pyo3::Py, #[covariant] dependent: RawTSTInfo, } @@ -336,16 +329,15 @@ impl PyTSTInfo { } } - // TODO(DM) Message Imprint - // #[getter] - // fn message_imprint(&self) -> PyResult { - // Ok(PyMessageImprint { - // contents: OwnedMessageImprint::try_new(Arc::clone(&self.raw), |v| { - // Ok::<_, ()>(v.borrow_dependent().message_imprint.clone()) - // }) - // .unwrap(), - // }) - // } + #[getter] + fn message_imprint(&self, py: pyo3::Python<'_>) -> PyResult { + Ok(PyMessageImprint { + contents: OwnedMessageImprint::try_new(self.raw.borrow_owner().clone_ref(py), |v| { + RawMessageImprint::parse_data(v.as_bytes(py)) + }) + .map_err(|_| PyValueError::new_err("invalid message imprint"))?, + }) + } #[getter] fn serial_number<'p>(&self, py: pyo3::Python<'p>) -> PyResult> { @@ -418,7 +410,7 @@ pub(crate) fn parse_timestamp_response( py: pyo3::Python<'_>, data: pyo3::Py, ) -> PyResult { - let raw = OwnedTimeStamResp::try_new(data, |data| asn1::parse_single(data.as_bytes(py))) + let raw = OwnedTimeStampResp::try_new(data, |data| asn1::parse_single(data.as_bytes(py))) .map_err(|e| { pyo3::exceptions::PyValueError::new_err(format!("ASN.1 parse error: {:?}", e)) })?; @@ -432,7 +424,7 @@ pub(crate) fn parse_timestamp_request( py: pyo3::Python<'_>, data: pyo3::Py, ) -> PyResult { - let raw = OwnedTimeStamReq::try_new(data, |data| asn1::parse_single(data.as_bytes(py))) + let raw = OwnedTimeStampReq::try_new(data, |data| asn1::parse_single(data.as_bytes(py))) .map_err(|e| { pyo3::exceptions::PyValueError::new_err(format!("ASN.1 parse error: {:?}", e)) })?; @@ -476,7 +468,7 @@ pub(crate) fn create_timestamp_request( .map_err(|e| PyValueError::new_err(format!("Serialization error: {:?}", e))); let py_bytes = pyo3::types::PyBytes::new_bound(py, &request_bytes.unwrap()).unbind(); - let raw = OwnedTimeStamReq::try_new(py_bytes, |data| asn1::parse_single(data.as_bytes(py))) + let raw = OwnedTimeStampReq::try_new(py_bytes, |data| asn1::parse_single(data.as_bytes(py))) .map_err(|e| { pyo3::exceptions::PyValueError::new_err(format!("ASN.1 parse error: {:?}", e)) })?; diff --git a/rust/tsp-asn1/Cargo.lock b/rust/tsp-asn1/Cargo.lock index f69af21..75df9c4 100644 --- a/rust/tsp-asn1/Cargo.lock +++ b/rust/tsp-asn1/Cargo.lock @@ -66,7 +66,7 @@ dependencies = [ ] [[package]] -name = "tsp-ans1" +name = "tsp-asn1" version = "0.0.1" dependencies = [ "asn1", diff --git a/rust/tsp-asn1/Cargo.toml b/rust/tsp-asn1/Cargo.toml index 5aae15d..033510d 100644 --- a/rust/tsp-asn1/Cargo.toml +++ b/rust/tsp-asn1/Cargo.toml @@ -3,7 +3,7 @@ name = "tsp-asn1" version = "0.0.1" edition = "2021" authors = [ - "Alexis Challande " + "Trail of Bits " ] publish = false diff --git a/rust/tsp-asn1/src/cms.rs b/rust/tsp-asn1/src/cms.rs index 5f093f4..c26c76d 100644 --- a/rust/tsp-asn1/src/cms.rs +++ b/rust/tsp-asn1/src/cms.rs @@ -1,23 +1,35 @@ +//! [RFC 5652] ("Cryptographic Message Syntax") definitions. +//! +//! [RFC 5652]: https://datatracker.ietf.org/doc/html/rfc5652 + use crate::certificate; -// IssuerAndSerialNumber ::= SEQUENCE { -// issuer Name, -// serialNumber CertificateSerialNumber } +/// RFC 5652 10.2.4 +/// +/// ```asn1 +/// IssuerAndSerialNumber ::= SEQUENCE { +/// issuer Name, +/// serialNumber CertificateSerialNumber } +/// +/// CertificateSerialNumber ::= INTEGER +/// ``` #[derive(asn1::Asn1Write, asn1::Asn1Read)] pub struct IssuerAndSerialNumber<'a> { pub issuer: cryptography_x509::name::Name<'a>, pub serial_number: asn1::BigInt<'a>, } -// https://datatracker.ietf.org/doc/html/rfc5652#section-5.3 -// SignerInfo ::= SEQUENCE { -// version CMSVersion, -// sid SignerIdentifier, -// digestAlgorithm DigestAlgorithmIdentifier, -// signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL, -// signatureAlgorithm SignatureAlgorithmIdentifier, -// signature SignatureValue, -// unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL } +/// RFC 5652 5.3 +/// ```asn1 +/// SignerInfo ::= SEQUENCE { +/// version CMSVersion, +/// sid SignerIdentifier, +/// digestAlgorithm DigestAlgorithmIdentifier, +/// signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL, +/// signatureAlgorithm SignatureAlgorithmIdentifier, +/// signature SignatureValue, +/// unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL } +/// ``` #[derive(asn1::Asn1Write, asn1::Asn1Read)] pub struct SignerInfo<'a> { pub version: u8, @@ -35,13 +47,17 @@ pub struct SignerInfo<'a> { pub unauthenticated_attributes: Option>, } -// SignedData ::= SEQUENCE { -// version CMSVersion, -// digestAlgorithms DigestAlgorithmIdentifiers, -// encapContentInfo EncapsulatedContentInfo, -// certificates [0] IMPLICIT CertificateSet OPTIONAL, -// crls [1] IMPLICIT RevocationInfoChoices OPTIONAL, -// signerInfos SignerInfos } +/// RFC 5652 5.1 +/// +/// ```asn1 +/// SignedData ::= SEQUENCE { +/// version CMSVersion, +/// digestAlgorithms DigestAlgorithmIdentifiers, +/// encapContentInfo EncapsulatedContentInfo, +/// certificates [0] IMPLICIT CertificateSet OPTIONAL, +/// crls [1] IMPLICIT RevocationInfoChoices OPTIONAL, +/// signerInfos SignerInfos } +/// ``` #[derive(asn1::Asn1Read, asn1::Asn1Write, Clone)] pub struct SignedData<'a> { pub version: u8, @@ -58,9 +74,13 @@ pub struct SignedData<'a> { pub signer_infos: asn1::SetOf<'a, SignerInfo<'a>>, } -// EncapsulatedContentInfo ::= SEQUENCE { -// eContentType ContentType, -// eContent [0] EXPLICIT OCTET STRING OPTIONAL } +/// RFC 5652 5.2 +/// +/// ```asn1 +/// EncapsulatedContentInfo ::= SEQUENCE { +/// eContentType ContentType, +/// eContent [0] EXPLICIT OCTET STRING OPTIONAL } +/// ``` #[derive(asn1::Asn1Read, asn1::Asn1Write, Clone)] pub struct ContentInfo<'a> { pub content_type: asn1::ObjectIdentifier, diff --git a/rust/tsp-asn1/src/tsp.rs b/rust/tsp-asn1/src/tsp.rs index 13d0764..8f0e113 100644 --- a/rust/tsp-asn1/src/tsp.rs +++ b/rust/tsp-asn1/src/tsp.rs @@ -1,21 +1,33 @@ -// MessageImprint ::= SEQUENCE { -// hashAlgorithm AlgorithmIdentifier, -// hashedMessage OCTET STRING } +//! [RFC 3161] definitions. +//! +//! [RFC 3161]: https://datatracker.ietf.org/doc/html/rfc3161 + +/// RFC 3161 2.4.1 +/// +/// ```asn1 +/// MessageImprint ::= SEQUENCE { +/// hashAlgorithm AlgorithmIdentifier, +/// hashedMessage OCTET STRING } +/// ``` #[derive(asn1::Asn1Read, asn1::Asn1Write, Clone)] pub struct MessageImprint<'a> { pub hash_algorithm: cryptography_x509::common::AlgorithmIdentifier<'a>, pub hashed_message: &'a [u8], } +/// RFC 3161 2.4.1 +/// +/// ```asn1 /// TimeStampReq ::= SEQUENCE { -// version INTEGER { v1(1) }, -// messageImprint MessageImprint, -// --a hash algorithm OID and the hash value of the data to be -// --time-stamped -// reqPolicy TSAPolicyId OPTIONAL, -// nonce INTEGER OPTIONAL, -// certReq BOOLEAN DEFAULT FALSE, -// extensions [0] IMPLICIT Extensions OPTIONAL } +/// version INTEGER { v1(1) }, +/// messageImprint MessageImprint, +/// --a hash algorithm OID and the hash value of the data to be +/// --time-stamped +/// reqPolicy TSAPolicyId OPTIONAL, +/// nonce INTEGER OPTIONAL, +/// certReq BOOLEAN DEFAULT FALSE, +/// extensions [0] IMPLICIT Extensions OPTIONAL } +/// ``` #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct RawTimeStampReq<'a> { pub version: u8, @@ -48,10 +60,14 @@ pub struct RawTimeStampReq<'a> { // -- notification that a revocation has occurred } // TODO(dm) = Implement me -// PKIStatusInfo ::= SEQUENCE { -// status PKIStatus, -// statusString PKIFreeText OPTIONAL, -// failInfo PKIFailureInfo OPTIONAL } +/// RFC 3161 2.4.2 +/// +/// ```asn1 +/// PKIStatusInfo ::= SEQUENCE { +/// status PKIStatus, +/// statusString PKIFreeText OPTIONAL, +/// failInfo PKIFailureInfo OPTIONAL } +/// ``` #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct PKIStatusInfo<'a> { pub status: u8, @@ -59,10 +75,14 @@ pub struct PKIStatusInfo<'a> { pub fail_info: Option>, } -// Accuracy ::= SEQUENCE { -// seconds INTEGER OPTIONAL, -// millis [0] INTEGER (1..999) OPTIONAL, -// micros [1] INTEGER (1..999) OPTIONAL } +/// RFC 3161 2.4.2 +/// +/// ```asn1 +/// Accuracy ::= SEQUENCE { +/// seconds INTEGER OPTIONAL, +/// millis [0] INTEGER (1..999) OPTIONAL, +/// micros [1] INTEGER (1..999) OPTIONAL } +/// ``` #[derive(asn1::Asn1Read, asn1::Asn1Write, Copy, Clone)] pub struct Accuracy<'a> { pub seconds: Option>, @@ -70,9 +90,13 @@ pub struct Accuracy<'a> { pub micros: Option, } -// TimeStampToken ::= ContentInfo -// -- contentType is id-signedData ([CMS]) -// -- content is SignedData ([CMS]) +/// RFC 3161 2.4.2 +/// +/// ```asn1 +/// TimeStampToken ::= ContentInfo +/// -- contentType is id-signedData ([CMS]) +/// -- content is SignedData ([CMS]) +/// ``` #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct TimeStampToken<'a> { pub _content_type: asn1::DefinedByMarker, @@ -83,6 +107,9 @@ pub struct TimeStampToken<'a> { pub const PKCS7_SIGNED_DATA_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 7, 2); +/// RFC 3161 2.4.2 +/// +/// See RFC 5652 for the definition of `SignedData`. #[derive(asn1::Asn1DefinedByWrite, asn1::Asn1DefinedByRead)] pub enum Content<'a> { #[defined_by(PKCS7_SIGNED_DATA_OID)] @@ -112,23 +139,27 @@ impl<'a> crate::cms::ContentInfo<'a> { } } -// TSTInfo ::= SEQUENCE { -// version INTEGER { v1(1) }, -// policy TSAPolicyId, -// messageImprint MessageImprint, -// -- MUST have the same value as the similar field in -// -- TimeStampReq -// serialNumber INTEGER, -// -- Time-Stamping users MUST be ready to accommodate integers -// -- up to 160 bits. -// genTime GeneralizedTime, -// accuracy Accuracy OPTIONAL, -// ordering BOOLEAN DEFAULT FALSE, -// nonce INTEGER OPTIONAL, -// -- MUST be present if the similar field was present -// -- in TimeStampReq. In that case it MUST have the same value. -// tsa [0] GeneralName OPTIONAL, -// extensions [1] IMPLICIT Extensions OPTIONAL } +/// RFC 3161 2.4.2 +/// +/// ```asn1 +/// TSTInfo ::= SEQUENCE { +/// version INTEGER { v1(1) }, +/// policy TSAPolicyId, +/// messageImprint MessageImprint, +/// -- MUST have the same value as the similar field in +/// -- TimeStampReq +/// serialNumber INTEGER, +/// -- Time-Stamping users MUST be ready to accommodate integers +/// -- up to 160 bits. +/// genTime GeneralizedTime, +/// accuracy Accuracy OPTIONAL, +/// ordering BOOLEAN DEFAULT FALSE, +/// nonce INTEGER OPTIONAL, +/// -- MUST be present if the similar field was present +/// -- in TimeStampReq. In that case it MUST have the same value. +/// tsa [0] GeneralName OPTIONAL, +/// extensions [1] IMPLICIT Extensions OPTIONAL } +/// ``` #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct TSTInfo<'a> { pub version: u8, @@ -144,9 +175,13 @@ pub struct TSTInfo<'a> { pub extensions: Option>, } -// TimeStampResp ::= SEQUENCE { -// status PKIStatusInfo, -// timeStampToken TimeStampToken OPTIONAL } +/// RFC 3161 2.4.2 +/// +/// ```asn1 +/// TimeStampResp ::= SEQUENCE { +/// status PKIStatusInfo, +/// timeStampToken TimeStampToken OPTIONAL } +/// ``` #[derive(asn1::Asn1Read, asn1::Asn1Write)] pub struct RawTimeStampResp<'a> { pub status: PKIStatusInfo<'a>, @@ -155,6 +190,8 @@ pub struct RawTimeStampResp<'a> { #[cfg(test)] mod tests { + use crate::{cms, name}; + use super::*; #[test] @@ -214,7 +251,8 @@ mod tests { assert_eq!(tst_info.version, 1); let enc_general_name = hex::decode("a482010d308201093111300f060355040a13084672656520545341310c300a060355040b130354534131763074060355040d136d54686973206365727469666963617465206469676974616c6c79207369676e7320646f63756d656e747320616e642074696d65207374616d70207265717565737473206d616465207573696e672074686520667265657473612e6f7267206f6e6c696e65207365727669636573311830160603550403130f7777772e667265657473612e6f72673122302006092a864886f70d0109011613627573696c657a617340676d61696c2e636f6d3112301006035504071309577565727a62757267310b3009060355040613024445310f300d0603550408130642617965726e").unwrap(); - let general_name = asn1::parse_single::(&enc_general_name).unwrap(); + let general_name = + asn1::parse_single::(&enc_general_name).unwrap(); let enc_seq_general_name = hex::decode("a0820111a482010d308201093111300f060355040a13084672656520545341310c300a060355040b130354534131763074060355040d136d54686973206365727469666963617465206469676974616c6c79207369676e7320646f63756d656e747320616e642074696d65207374616d70207265717565737473206d616465207573696e672074686520667265657473612e6f7267206f6e6c696e65207365727669636573311830160603550403130f7777772e667265657473612e6f72673122302006092a864886f70d0109011613627573696c657a617340676d61696c2e636f6d3112301006035504071309577565727a62757267310b3009060355040613024445310f300d0603550408130642617965726e").unwrap(); let general_name = diff --git a/src/sigstore_tsp/tsp.py b/src/sigstore_tsp/tsp.py index a69764a..a3f551b 100644 --- a/src/sigstore_tsp/tsp.py +++ b/src/sigstore_tsp/tsp.py @@ -1,13 +1,16 @@ from __future__ import annotations import abc -import datetime import enum - -import cryptography.x509 +import typing from sigstore_tsp import _rust +if typing.TYPE_CHECKING: + import datetime + + import cryptography.x509 + class ObjectIdentifier(metaclass=abc.ABCMeta): @property @@ -15,11 +18,13 @@ class ObjectIdentifier(metaclass=abc.ABCMeta): def dotted_string(self) -> str: """Returns the dotted string of the OID.""" + ObjectIdentifier.register(_rust.ObjectIdentifier) class MessageImprint(metaclass=abc.ABCMeta): """Represents a Message Imprint (per RFC 3161).""" + @property @abc.abstractmethod def hash_algorithm(self) -> ObjectIdentifier: @@ -30,8 +35,10 @@ def hash_algorithm(self) -> ObjectIdentifier: def message(self) -> bytes: """Return the hashed message.""" + MessageImprint.register(_rust.PyMessageImprint) + class TimeStampRequest(metaclass=abc.ABCMeta): """Represents a Timestamp Request (per RFC 3161).""" @@ -67,6 +74,7 @@ def as_bytes(self) -> bytes: TimeStampRequest.register(_rust.TimeStampReq) + class PKIStatus(enum.IntEnum): GRANTED = 0 GRANTED_WITH_MODS = 1 @@ -77,7 +85,6 @@ class PKIStatus(enum.IntEnum): class TimeStampResponse(metaclass=abc.ABCMeta): - @property @abc.abstractmethod def status(self) -> int: @@ -98,11 +105,11 @@ def tst_info(self) -> TimeStampTokenInfo: def signed_data(self) -> SignedData: """Returns the Signed Data.""" + TimeStampResponse.register(_rust.TimeStampResp) class Accuracy(metaclass=abc.ABCMeta): - @property @abc.abstractmethod def seconds(self) -> int: @@ -113,12 +120,12 @@ def seconds(self) -> int: def millis(self) -> int | None: """Returns the seconds.""" - @property @abc.abstractmethod def micros(self) -> int | None: """Returns the seconds.""" + Accuracy.register(_rust.Accuracy) @@ -168,7 +175,6 @@ def name(self) -> cryptography.x509.Name: class SignedData(metaclass=abc.ABCMeta): - @property @abc.abstractmethod def version(self) -> int: @@ -186,4 +192,5 @@ def certificates(self) -> set[bytes]: Warning: they are returned as a byte array and should be loaded. """ + SignedData.register(_rust.SignedData) diff --git a/src/sigstore_tsp/verify.py b/src/sigstore_tsp/verify.py index 19dde4a..3d66650 100644 --- a/src/sigstore_tsp/verify.py +++ b/src/sigstore_tsp/verify.py @@ -1,10 +1,11 @@ """Verification module.""" + from dataclasses import dataclass -from logging import critical import cryptography.x509 -from sigstore_tsp.tsp import TimeStampRequest, TimeStampResponse, PKIStatus, ObjectIdentifier +from sigstore_tsp.tsp import ObjectIdentifier, PKIStatus, TimeStampRequest, TimeStampResponse + @dataclass class VerifyOpts: @@ -64,7 +65,10 @@ def _verify_leaf_certs(tsp_response: TimeStampResponse, opts: VerifyOpts) -> boo # verifyESSCertID if opts.tsa_certificate: - if leaf_certificate.issuer != opts.tsa_certificate.issuer or leaf_certificate.serial_number != opts.tsa_certificate.serial_number: + if ( + leaf_certificate.issuer != opts.tsa_certificate.issuer + or leaf_certificate.serial_number != opts.tsa_certificate.serial_number + ): return False # verifySubjectCommonName @@ -88,6 +92,7 @@ def _verify_tsr_with_chains(tsp_response: TimeStampResponse, opts: VerifyOpts) - # TODO(dm) return True + def verify_timestamp_response( timestamp_response: TimeStampResponse, hashed_message: bytes, verify_opts: VerifyOpts ) -> bool: