Skip to content

Commit

Permalink
Update code
Browse files Browse the repository at this point in the history
  • Loading branch information
DarkaMaul committed Sep 27, 2024
1 parent 8402ff1 commit 1177313
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 24 deletions.
15 changes: 12 additions & 3 deletions rust/src/cms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use crate::name;
// IssuerAndSerialNumber ::= SEQUENCE {
// issuer Name,
// serialNumber CertificateSerialNumber }

#[derive(asn1::Asn1Write, asn1::Asn1Read)]
pub struct IssuerAndSerialNumber<'a> {
pub issuer: name::Name<'a>,
Expand All @@ -22,11 +21,10 @@ pub struct IssuerAndSerialNumber<'a> {
// signatureAlgorithm SignatureAlgorithmIdentifier,
// signature SignatureValue,
// unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL }

#[derive(asn1::Asn1Write, asn1::Asn1Read)]
pub struct SignerInfo<'a> {
pub version: u8,
// Of note, this is not exactly the standard here because we are not implementing
// Of note, this is a slight deviation from the standard here because we are not implementing
// the SignerIdentifier CHOICE.
pub issuer_and_serial_number: IssuerAndSerialNumber<'a>,
pub digest_algorithm: common::AlgorithmIdentifier<'a>,
Expand All @@ -40,6 +38,13 @@ pub struct SignerInfo<'a> {
pub unauthenticated_attributes: Option<csr::Attributes<'a>>,
}

// 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)]
pub(crate) struct SignedData<'a> {
pub version: u8,
Expand All @@ -52,9 +57,13 @@ pub(crate) struct SignedData<'a> {
#[implicit(1)]
pub crls: Option<asn1::Sequence<'a>>,

// SignerInfos ::= SET OF SignerInfo
pub signer_infos: asn1::SetOf<'a, SignerInfo<'a>>,
}

// EncapsulatedContentInfo ::= SEQUENCE {
// eContentType ContentType,
// eContent [0] EXPLICIT OCTET STRING OPTIONAL }
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
pub(crate) struct ContentInfo<'a> {
pub content_type: asn1::ObjectIdentifier,
Expand Down
117 changes: 104 additions & 13 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,79 @@ pub struct TimeStampReq {
raw: OwnedTimeStamReq,
}

pub(crate) fn big_asn1_uint_to_py<'p>(
py: pyo3::Python<'p>,
v: asn1::BigUint<'_>,
) -> PyResult<pyo3::Bound<'p, pyo3::PyAny>> {
let int_type = py.get_type_bound::<pyo3::types::PyLong>();
Ok(int_type.call_method1(
pyo3::intern!(py, "from_bytes"),
(v.as_bytes(), pyo3::intern!(py, "big")),
)?)
}

pub(crate) fn oid_to_py_oid<'p>(
py: pyo3::Python<'p>,
oid: &asn1::ObjectIdentifier,
) -> pyo3::PyResult<pyo3::Bound<'p, pyo3::PyAny>> {
Ok(pyo3::Bound::new(py, ObjectIdentifier { oid: oid.clone() })?.into_any())
}

#[pyo3::pyclass(frozen, module = "sigstore_tsp._rust")]
pub(crate) struct ObjectIdentifier {
pub(crate) oid: asn1::ObjectIdentifier,
}

#[pyo3::pymethods]
impl ObjectIdentifier {
#[new]
fn new(value: &str) -> PyResult<Self> {
let oid = asn1::ObjectIdentifier::from_string(value)
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("Invalid value"))?;
Ok(ObjectIdentifier { oid })
}

#[getter]
fn dotted_string(&self) -> String {
self.oid.to_string()
}
}

#[pyo3::pymethods]
impl TimeStampReq {
#[getter]
fn version(&self) -> PyResult<u8> {
Ok(self.raw.borrow_dependent().version)
}

#[getter]
fn nonce<'p>(&self, py: pyo3::Python<'p>) -> PyResult<pyo3::Bound<'p, pyo3::PyAny>> {
match self.raw.borrow_dependent().nonce {
Some(nonce) => {
let py_nonce = big_asn1_uint_to_py(py, nonce)?;
Ok(py_nonce)
}
None => todo!(),
}
}

fn as_bytes<'p>(
&self,
py: pyo3::Python<'p>,
) -> PyResult<pyo3::Bound<'p, pyo3::types::PyBytes>> {
let result = asn1::write_single(&self.raw.borrow_dependent());
match result {
Ok(request_bytes) => {
Ok(pyo3::types::PyBytes::new_bound(py, &request_bytes))
},
Err(e) => {
Err(pyo3::exceptions::PyValueError::new_err(format!("{e}")))
}
Ok(request_bytes) => Ok(pyo3::types::PyBytes::new_bound(py, &request_bytes)),
Err(e) => Err(pyo3::exceptions::PyValueError::new_err(format!("{e}"))),
}
}

#[getter]
fn policy<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<pyo3::Bound<'p, pyo3::PyAny>> {
match &self.raw.borrow_dependent().req_policy {
Some(req_policy) => oid_to_py_oid(py, &req_policy),
None => todo!(),
}

}
}

Expand All @@ -63,6 +115,21 @@ pub struct TimeStampResp {
raw: OwnedTimeStamResp,
}

impl TimeStampResp {
fn get_tst_info(&self) -> pyo3::PyResult<tsp::TSTInfo<'_>> {
let timestamp_token = self.raw.borrow_dependent().time_stamp_token.as_ref();
match timestamp_token {
Some(content) => match &content.content {
tsp::Content::SignedData(signed_data) => {
let tst_info = signed_data.as_inner().content_info.tst_info().unwrap();
Ok(tst_info)
}
},
None => Err(pyo3::exceptions::PyValueError::new_err("")),
}
}
}

#[pyo3::pymethods]
impl TimeStampResp {
#[getter]
Expand Down Expand Up @@ -93,12 +160,36 @@ impl TimeStampResp {
}
}

// #[getter]
// fn fail_info(
// &self,
// ) -> pyo3::PyResult<String> {
// Ok(self.raw.borrow_dependent().status.status)
// }
// TST INFO
#[getter]
fn tst_info_version(&self) -> pyo3::PyResult<u8> {
let tst_info = self.get_tst_info()?;
Ok(tst_info.version)
}

#[getter]
fn tst_info_nonce<'p>(&self, py: pyo3::Python<'p>) -> PyResult<pyo3::Bound<'p, pyo3::PyAny>> {
let tst_info = self.get_tst_info()?;
match tst_info.nonce {
Some(nonce) => {
let py_nonce = big_asn1_uint_to_py(py, nonce)?;
Ok(py_nonce)
}
None => todo!(),
}
}

#[getter]
fn tst_info_policy<'p>(
&self,
py: pyo3::Python<'p>,
) -> pyo3::PyResult<pyo3::Bound<'p, pyo3::PyAny>> {
let tst_info = self.get_tst_info()?;
match tst_info.policy {
Some(policy_id) => oid_to_py_oid(py, &policy_id),
None => todo!(),
}
}
}

#[pyo3::pyfunction]
Expand Down
8 changes: 4 additions & 4 deletions rust/src/tsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub(crate) struct PKIStatusInfo<'a> {
// millis [0] INTEGER (1..999) OPTIONAL,
// micros [1] INTEGER (1..999) OPTIONAL }
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
struct Accuracy<'a> {
pub(crate) struct Accuracy<'a> {
seconds: Option<asn1::BigUint<'a>>,
millis: Option<u8>,
micros: Option<u8>,
Expand All @@ -79,17 +79,17 @@ struct Accuracy<'a> {
// -- contentType is id-signedData ([CMS])
// -- content is SignedData ([CMS])
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
struct TimeStampToken<'a> {
pub struct TimeStampToken<'a> {
pub _content_type: asn1::DefinedByMarker<asn1::ObjectIdentifier>,

#[defined_by(_content_type)]
pub content: Content<'a>,
pub(crate) content: Content<'a>,
}

pub const PKCS7_SIGNED_DATA_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 7, 2);

#[derive(asn1::Asn1DefinedByWrite, asn1::Asn1DefinedByRead)]
pub enum Content<'a> {
pub(crate) enum Content<'a> {
#[defined_by(PKCS7_SIGNED_DATA_OID)]
SignedData(asn1::Explicit<Box<cms::SignedData<'a>>, 0>),
}
Expand Down
27 changes: 25 additions & 2 deletions src/sigstore_tsp/_rust/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,12 +1,35 @@
class TimeStampRequest: ...
class TimeStampRequest:
@property
def nonce(self) -> int: ...

@property
def version(self) -> int: ...

@property
def policy(self) -> ObjectIdentifier: ...

def create_timestamp_request(
data: bytes,
) -> TimeStampRequest: ...


class TimeStampResponse: ...
class TimeStampResponse:
@property
def status(self) -> int: ...

@property
def tst_info_version(self) -> int: ...

@property
def tst_info_nonce(self) -> int: ...

@property
def tst_info_policy(self) -> ObjectIdentifier: ...

def parse_timestamp_response(
data: bytes,
) -> TimeStampResponse: ...

class ObjectIdentifier:
@property
def dotted_string(self) -> str:...
56 changes: 54 additions & 2 deletions src/sigstore_tsp/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@ class HashAlgorithm(enum.Enum):
_AllowedHashTypes = HashAlgorithm


class TimestampRequest:
def __init__(self, raw: _rust.TimeStampRequest) -> None:
self.raw = raw

@property
def nonce(self) -> int:
return self.raw.nonce

@property
def policy_oid(self) -> _rust.ObjectIdentifier:
return self.raw.policy


class TimestampRequestBuilder:
"""Timestamp Request Builder class."""

Expand Down Expand Up @@ -93,6 +106,45 @@ def build(self) -> _rust.TimeStampRequest:
return _rust.create_timestamp_request(self._data)


def decode_timestamp_response(data: bytes) -> _rust.TimeStampResponse:
# // PKIStatus ::= INTEGER {
# // granted (0),
# // -- when the PKIStatus contains the value zero a TimeStampToken, as
# // requested, is present.
# // grantedWithMods (1),
# // -- when the PKIStatus contains the value one a TimeStampToken,
# // with modifications, is present.
# // rejection (2),
# // waiting (3),
# // revocationWarning (4),
# // -- this message contains a warning that a revocation is
# // -- imminent
# // revocationNotification (5)
# // -- notification that a revocation has occurred }
class PKIStatus(enum.IntEnum):
GRANTED = 0
GRANTED_WITH_MODS = 1
REJECTION = 2
WAITING = 3
REVOCATION_WARNING = 4
REVOCATION_NOTIFICATION = 5


class TstInfo:
def __init__(self, raw: _rust.TimeStampResponse) -> None:
self.version: int = raw.tst_info_version
self.policy: _rust.ObjectIdentifier = raw.tst_info_policy


class TimestampResponse:
def __init__(self, raw: _rust.TimeStampResponse) -> None:
self.raw: _rust.TimeStampResponse = raw
self.tst_info: TstInfo = TstInfo(raw)

@property
def status(self) -> PKIStatus:
return PKIStatus(self.raw.status)


def decode_timestamp_response(data: bytes) -> TimestampResponse:
"""Decode a Timestamp response."""
return _rust.parse_timestamp_response(data)
return TimestampResponse(_rust.parse_timestamp_response(data))
42 changes: 42 additions & 0 deletions src/sigstore_tsp/verify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Verification module."""

from sigstore_tsp.base import PKIStatus, TimestampRequest, TimestampResponse


def verify_signed_timestamp(
signed_timestamp: TimestampResponse, timestamp_request: TimestampRequest
) -> bool:
"""Verify a Timestamp Response.
Inspired by:
https://github.com/sigstore/timestamp-authority/blob/main/pkg/verification/verify.go#L209
"""
# Note: digitorus/timestamp does not validate if the result is GRANTED_WITH_MOD
# https://github.com/digitorus/timestamp/blob/master/timestamp.go#L268
if signed_timestamp.status == PKIStatus.GRANTED:
return False

# TODO(dm): verifyTSRWithChain

# Verify Nonce
if timestamp_request.nonce is not None:
return False

if (
timestamp_request.policy_oid is not None
and timestamp_request.policy_oid != signed_timestamp.tst_info.policy
):
return False

# TODO(dm)
# if err = verifyLeafCert(*ts, opts); err != nil {
# return nil, err
# }

# // verify the hash in the timestamp response matches the artifact hash
# if err = verifyHashedMessages(ts.HashAlgorithm.New(), ts.HashedMessage, artifact); err != nil {
# return nil, err
# }

return True

0 comments on commit 1177313

Please sign in to comment.