use const_oid::ObjectIdentifier;
use der::{
asn1::{BitString, GeneralizedTime, Int, OctetString, Uint},
Decode, Encode, Sequence,
};
use rand::Rng;
use sigstore_types::HashAlgorithm;
use x509_cert::{ext::pkix::name::GeneralName, ext::Extensions};
pub const OID_SHA256: ObjectIdentifier = const_oid::db::rfc5912::ID_SHA_256;
pub const OID_SHA384: ObjectIdentifier = const_oid::db::rfc5912::ID_SHA_384;
pub const OID_SHA512: ObjectIdentifier = const_oid::db::rfc5912::ID_SHA_512;
pub const OID_TST_INFO: ObjectIdentifier =
ObjectIdentifier::new_unwrap("1.2.840.113549.1.9.16.1.4");
pub fn generate_nonce() -> Int {
let nonce: u64 = rand::rng().random();
let uint = Uint::new(&nonce.to_be_bytes()).expect("valid uint from random u64");
Int::from(uint)
}
#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
pub struct AlgorithmIdentifier {
pub algorithm: ObjectIdentifier,
#[asn1(optional = "true")]
pub parameters: Option<der::Any>,
}
impl AlgorithmIdentifier {
pub fn sha256() -> Self {
Self {
algorithm: OID_SHA256,
parameters: None,
}
}
pub fn sha384() -> Self {
Self {
algorithm: OID_SHA384,
parameters: None,
}
}
pub fn sha512() -> Self {
Self {
algorithm: OID_SHA512,
parameters: None,
}
}
pub fn to_hash_algorithm(&self) -> Option<HashAlgorithm> {
match self.algorithm {
OID_SHA256 => Some(HashAlgorithm::Sha2256),
OID_SHA384 => Some(HashAlgorithm::Sha2384),
OID_SHA512 => Some(HashAlgorithm::Sha2512),
_ => None,
}
}
}
impl From<HashAlgorithm> for AlgorithmIdentifier {
fn from(algo: HashAlgorithm) -> Self {
match algo {
HashAlgorithm::Sha2256 => Self::sha256(),
HashAlgorithm::Sha2384 => Self::sha384(),
HashAlgorithm::Sha2512 => Self::sha512(),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
pub struct Asn1MessageImprint {
pub hash_algorithm: AlgorithmIdentifier,
pub hashed_message: OctetString,
}
impl Asn1MessageImprint {
pub fn new(algorithm: AlgorithmIdentifier, digest: Vec<u8>) -> Self {
Self {
hash_algorithm: algorithm,
hashed_message: OctetString::new(digest).expect("valid octet string"),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
pub struct TimeStampReq {
pub version: u8,
pub message_imprint: Asn1MessageImprint,
#[asn1(optional = "true")]
pub req_policy: Option<ObjectIdentifier>,
#[asn1(optional = "true")]
pub nonce: Option<Int>,
#[asn1(default = "default_false")]
pub cert_req: bool,
}
fn default_false() -> bool {
false
}
impl TimeStampReq {
pub fn new(message_imprint: Asn1MessageImprint) -> Self {
Self {
version: 1,
message_imprint,
req_policy: None,
nonce: Some(generate_nonce()),
cert_req: true,
}
}
pub fn new_without_nonce(message_imprint: Asn1MessageImprint) -> Self {
Self {
version: 1,
message_imprint,
req_policy: None,
nonce: None,
cert_req: true,
}
}
pub fn with_nonce(mut self, nonce: u64) -> Self {
let uint = Uint::new(&nonce.to_be_bytes()).expect("valid unsigned integer");
self.nonce = Some(Int::from(uint));
self
}
pub fn with_cert_req(mut self, cert_req: bool) -> Self {
self.cert_req = cert_req;
self
}
pub fn to_der(&self) -> Result<Vec<u8>, der::Error> {
Encode::to_der(self)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum PkiStatus {
Granted = 0,
GrantedWithMods = 1,
Rejection = 2,
Waiting = 3,
RevocationWarning = 4,
RevocationNotification = 5,
}
impl TryFrom<u8> for PkiStatus {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(PkiStatus::Granted),
1 => Ok(PkiStatus::GrantedWithMods),
2 => Ok(PkiStatus::Rejection),
3 => Ok(PkiStatus::Waiting),
4 => Ok(PkiStatus::RevocationWarning),
5 => Ok(PkiStatus::RevocationNotification),
_ => Err(()),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
pub struct PkiStatusInfo {
pub status: u8,
#[asn1(optional = "true")]
pub status_string: Option<Vec<String>>,
#[asn1(optional = "true")]
pub fail_info: Option<BitString>,
}
impl PkiStatusInfo {
pub fn is_success(&self) -> bool {
self.status == PkiStatus::Granted as u8 || self.status == PkiStatus::GrantedWithMods as u8
}
pub fn status_enum(&self) -> Option<PkiStatus> {
PkiStatus::try_from(self.status).ok()
}
}
#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
pub struct Accuracy {
#[asn1(optional = "true")]
pub seconds: Option<u64>,
#[asn1(context_specific = "0", optional = "true")]
pub millis: Option<u16>,
#[asn1(context_specific = "1", optional = "true")]
pub micros: Option<u16>,
}
#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
pub struct TstInfo {
pub version: u8,
pub policy: ObjectIdentifier,
pub message_imprint: Asn1MessageImprint,
pub serial_number: Int,
pub gen_time: GeneralizedTime,
#[asn1(optional = "true")]
pub accuracy: Option<Accuracy>,
#[asn1(default = "default_false")]
pub ordering: bool,
#[asn1(optional = "true")]
pub nonce: Option<Int>,
#[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")]
pub tsa: Option<GeneralName>,
#[asn1(context_specific = "1", optional = "true", tag_mode = "IMPLICIT")]
pub extensions: Option<Extensions>,
}
impl TstInfo {
pub fn from_der_bytes(bytes: &[u8]) -> Result<Self, der::Error> {
Self::from_der(bytes)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
pub struct TimeStampResp {
pub status: PkiStatusInfo,
#[asn1(optional = "true")]
pub time_stamp_token: Option<der::Any>,
}
impl TimeStampResp {
pub fn from_der_bytes(bytes: &[u8]) -> Result<Self, der::Error> {
Self::from_der(bytes)
}
pub fn is_success(&self) -> bool {
self.status.is_success() && self.time_stamp_token.is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_message_imprint_encode() {
let digest = vec![0u8; 32]; let imprint = Asn1MessageImprint::new(AlgorithmIdentifier::sha256(), digest);
let der = Encode::to_der(&imprint).unwrap();
assert!(!der.is_empty());
}
#[test]
fn test_timestamp_req_encode() {
let digest = vec![0u8; 32];
let imprint = Asn1MessageImprint::new(AlgorithmIdentifier::sha256(), digest);
let req = TimeStampReq::new(imprint);
let der = req.to_der().unwrap();
assert!(!der.is_empty());
}
#[test]
fn test_timestamp_req_has_nonce() {
let digest = vec![0u8; 32];
let imprint = Asn1MessageImprint::new(AlgorithmIdentifier::sha256(), digest);
let req = TimeStampReq::new(imprint);
assert!(
req.nonce.is_some(),
"Nonce should be automatically generated"
);
}
#[test]
fn test_generate_nonce_roundtrips_as_canonical_der() {
for _ in 0..1000 {
let nonce = generate_nonce();
let encoded = Encode::to_der(&nonce).expect("DER encoding must succeed");
let decoded = Int::from_der(&encoded);
assert!(
decoded.is_ok(),
"nonce failed canonical DER round-trip: {:02x?} → {:?}",
encoded,
decoded.err()
);
}
}
#[test]
fn test_uint_produces_canonical_der_for_problematic_patterns() {
let cases: &[&[u8]] = &[
&[0x00, 0x35], &[0x00, 0xFF], &[0x00, 0x00, 0x42], &[0x00, 0x00, 0x00, 0x01], ];
for input in cases {
let uint = Uint::new(input).unwrap();
let int = Int::from(uint);
let encoded = Encode::to_der(&int).unwrap();
let decoded = Int::from_der(&encoded);
assert!(
decoded.is_ok(),
"input {:02x?} produced non-canonical DER: {:02x?} → {:?}",
input,
encoded,
decoded.err()
);
}
}
#[test]
fn test_pki_status() {
assert!(PkiStatus::try_from(0).is_ok());
assert!(PkiStatus::try_from(5).is_ok());
assert!(PkiStatus::try_from(6).is_err());
}
}