Skip to main content

sigstore_types/
encoding.rs

1//! Encoding helpers and concrete types for sigstore
2//!
3//! This module provides concrete types with semantic meaning that handle
4//! encoding/decoding internally. Each type represents a specific kind of data
5//! and serializes appropriately (usually as base64).
6//!
7//! The design philosophy is:
8//! - Use concrete newtype wrappers with semantic meaning
9//! - Types handle their own encoding/decoding via serde
10//! - Clear type names prevent mixing up different kinds of data
11
12use crate::error::{Error, Result};
13use base64::Engine;
14use serde::{Deserialize, Serialize};
15
16// ============================================================================
17// Serde helper modules (for use with raw Vec<u8> when needed)
18// ============================================================================
19
20/// Serde helper for base64 encoding/decoding of byte arrays
21///
22/// Use this with `#[serde(with = "base64_bytes")]` on `Vec<u8>` fields.
23pub mod base64_bytes {
24    use base64::{engine::general_purpose::STANDARD, Engine};
25    use serde::{Deserialize, Deserializer, Serializer};
26
27    pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
28    where
29        S: Serializer,
30    {
31        serializer.serialize_str(&STANDARD.encode(bytes))
32    }
33
34    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
35    where
36        D: Deserializer<'de>,
37    {
38        let s = String::deserialize(deserializer)?;
39        STANDARD.decode(s).map_err(serde::de::Error::custom)
40    }
41}
42
43/// Serde helper for optional base64 encoding/decoding
44pub mod base64_bytes_option {
45    use base64::{engine::general_purpose::STANDARD, Engine};
46    use serde::{Deserialize, Deserializer, Serializer};
47
48    pub fn serialize<S>(bytes: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
49    where
50        S: Serializer,
51    {
52        match bytes {
53            Some(b) => serializer.serialize_some(&STANDARD.encode(b)),
54            None => serializer.serialize_none(),
55        }
56    }
57
58    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
59    where
60        D: Deserializer<'de>,
61    {
62        let opt: Option<String> = Option::deserialize(deserializer)?;
63        match opt {
64            Some(s) => STANDARD
65                .decode(s)
66                .map(Some)
67                .map_err(serde::de::Error::custom),
68            None => Ok(None),
69        }
70    }
71}
72
73/// Serde helper for hex encoding/decoding of byte arrays
74pub mod hex_bytes {
75    use serde::{Deserialize, Deserializer, Serializer};
76
77    pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
78    where
79        S: Serializer,
80    {
81        serializer.serialize_str(&hex::encode(bytes))
82    }
83
84    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
85    where
86        D: Deserializer<'de>,
87    {
88        let s = String::deserialize(deserializer)?;
89        hex::decode(s).map_err(serde::de::Error::custom)
90    }
91}
92
93/// Serde helper for i64 fields serialized as strings
94///
95/// JSON bundles use strings for large integers. This helper serializes
96/// i64 values as strings and parses them back.
97pub mod string_i64 {
98    use serde::{Deserialize, Deserializer, Serializer};
99
100    pub fn serialize<S>(value: &i64, serializer: S) -> Result<S::Ok, S::Error>
101    where
102        S: Serializer,
103    {
104        serializer.serialize_str(&value.to_string())
105    }
106
107    pub fn deserialize<'de, D>(deserializer: D) -> Result<i64, D::Error>
108    where
109        D: Deserializer<'de>,
110    {
111        let s = String::deserialize(deserializer)?;
112        s.parse::<i64>()
113            .map_err(|_| serde::de::Error::custom(format!("invalid integer: {}", s)))
114    }
115}
116
117// ============================================================================
118// Macro for creating base64-encoded newtype wrappers
119// ============================================================================
120
121macro_rules! base64_newtype {
122    ($(#[$meta:meta])* $name:ident) => {
123        $(#[$meta])*
124        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
125        pub struct $name(Vec<u8>);
126
127        impl $name {
128            /// Create from raw bytes
129            pub fn new(bytes: Vec<u8>) -> Self {
130                Self(bytes)
131            }
132
133            /// Create from a byte slice
134            pub fn from_bytes(bytes: &[u8]) -> Self {
135                Self(bytes.to_vec())
136            }
137
138            /// Create from base64-encoded string
139            pub fn from_base64(s: &str) -> Result<Self> {
140                let bytes = base64::engine::general_purpose::STANDARD
141                    .decode(s)
142                    .map_err(|e| Error::InvalidEncoding(format!("invalid base64: {}", e)))?;
143                Ok(Self(bytes))
144            }
145
146            /// Encode as base64 string
147            pub fn to_base64(&self) -> String {
148                base64::engine::general_purpose::STANDARD.encode(&self.0)
149            }
150
151            /// Get the raw bytes
152            pub fn as_bytes(&self) -> &[u8] {
153                &self.0
154            }
155
156            /// Consume and return the inner bytes
157            pub fn into_bytes(self) -> Vec<u8> {
158                self.0
159            }
160
161            /// Get the length in bytes
162            pub fn len(&self) -> usize {
163                self.0.len()
164            }
165
166            /// Check if empty
167            pub fn is_empty(&self) -> bool {
168                self.0.is_empty()
169            }
170        }
171
172        impl AsRef<[u8]> for $name {
173            fn as_ref(&self) -> &[u8] {
174                &self.0
175            }
176        }
177
178        impl From<Vec<u8>> for $name {
179            fn from(bytes: Vec<u8>) -> Self {
180                Self(bytes)
181            }
182        }
183
184        impl From<&[u8]> for $name {
185            fn from(bytes: &[u8]) -> Self {
186                Self(bytes.to_vec())
187            }
188        }
189
190        impl std::fmt::Display for $name {
191            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192                write!(f, "{}", self.to_base64())
193            }
194        }
195
196        impl serde::Serialize for $name {
197            fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
198            where
199                S: serde::Serializer,
200            {
201                serializer.serialize_str(&self.to_base64())
202            }
203        }
204
205        impl<'de> serde::Deserialize<'de> for $name {
206            fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
207            where
208                D: serde::Deserializer<'de>,
209            {
210                let s = String::deserialize(deserializer)?;
211                Self::from_base64(&s).map_err(serde::de::Error::custom)
212            }
213        }
214    };
215}
216
217// ============================================================================
218// Concrete Types for Different Kinds of Binary Data
219// ============================================================================
220
221base64_newtype!(
222    /// DER-encoded X.509 certificate bytes
223    ///
224    /// This type represents a certificate in DER format (binary ASN.1).
225    /// Serializes as base64 in JSON.
226    ///
227    /// # Example
228    /// ```
229    /// use sigstore_types::DerCertificate;
230    ///
231    /// // Parse from PEM (validates CERTIFICATE header)
232    /// let pem = "-----BEGIN CERTIFICATE-----\nYWJjZA==\n-----END CERTIFICATE-----";
233    /// let cert = DerCertificate::from_pem(pem).unwrap();
234    ///
235    /// // Convert back to PEM
236    /// let pem_out = cert.to_pem();
237    /// ```
238    DerCertificate
239);
240
241impl DerCertificate {
242    /// Parse from PEM-encoded certificate string.
243    ///
244    /// Validates that the PEM block has a `CERTIFICATE` header.
245    /// Returns an error if the PEM is invalid or has the wrong type.
246    pub fn from_pem(pem_str: &str) -> Result<Self> {
247        let parsed = pem::parse(pem_str)
248            .map_err(|e| Error::InvalidEncoding(format!("failed to parse PEM: {}", e)))?;
249
250        if parsed.tag() != "CERTIFICATE" {
251            return Err(Error::InvalidEncoding(format!(
252                "expected CERTIFICATE PEM block, got {}",
253                parsed.tag()
254            )));
255        }
256
257        Ok(Self::new(parsed.contents().to_vec()))
258    }
259
260    /// Encode as PEM string with CERTIFICATE header.
261    pub fn to_pem(&self) -> String {
262        let pem_block = pem::Pem::new("CERTIFICATE", self.as_bytes());
263        pem::encode(&pem_block)
264    }
265}
266
267base64_newtype!(
268    /// DER-encoded public key bytes (SubjectPublicKeyInfo format)
269    ///
270    /// This type represents a public key in DER format.
271    /// Serializes as base64 in JSON.
272    ///
273    /// # Example
274    /// ```
275    /// use sigstore_types::DerPublicKey;
276    ///
277    /// // Parse from PEM (validates PUBLIC KEY header)
278    /// let pem = "-----BEGIN PUBLIC KEY-----\nYWJjZA==\n-----END PUBLIC KEY-----";
279    /// let key = DerPublicKey::from_pem(pem).unwrap();
280    ///
281    /// // Convert back to PEM
282    /// let pem_out = key.to_pem();
283    /// ```
284    DerPublicKey
285);
286
287impl DerPublicKey {
288    /// Parse from PEM-encoded public key string.
289    ///
290    /// Validates that the PEM block has a `PUBLIC KEY` header.
291    /// Returns an error if the PEM is invalid or has the wrong type.
292    pub fn from_pem(pem_str: &str) -> Result<Self> {
293        let parsed = pem::parse(pem_str)
294            .map_err(|e| Error::InvalidEncoding(format!("failed to parse PEM: {}", e)))?;
295
296        if parsed.tag() != "PUBLIC KEY" {
297            return Err(Error::InvalidEncoding(format!(
298                "expected PUBLIC KEY PEM block, got {}",
299                parsed.tag()
300            )));
301        }
302
303        Ok(Self::new(parsed.contents().to_vec()))
304    }
305
306    /// Encode as PEM string with PUBLIC KEY header.
307    pub fn to_pem(&self) -> String {
308        let pem_block = pem::Pem::new("PUBLIC KEY", self.as_bytes());
309        pem::encode(&pem_block)
310    }
311}
312
313base64_newtype!(
314    /// Cryptographic signature bytes
315    ///
316    /// This type represents raw signature bytes (format depends on algorithm).
317    /// Serializes as base64 in JSON.
318    SignatureBytes
319);
320
321base64_newtype!(
322    /// DSSE payload bytes
323    ///
324    /// This type represents the payload content of a DSSE envelope.
325    /// Serializes as base64 in JSON.
326    PayloadBytes
327);
328
329base64_newtype!(
330    /// Canonicalized Rekor entry body
331    ///
332    /// This type represents the canonicalized JSON body of a Rekor log entry.
333    /// Serializes as base64 in JSON.
334    CanonicalizedBody
335);
336
337base64_newtype!(
338    /// Signed Entry Timestamp (SET) bytes
339    ///
340    /// This type represents a signed timestamp from the transparency log.
341    /// Serializes as base64 in JSON.
342    SignedTimestamp
343);
344
345base64_newtype!(
346    /// RFC 3161 timestamp token bytes
347    ///
348    /// This type represents a DER-encoded RFC 3161 timestamp response.
349    /// Serializes as base64 in JSON.
350    TimestampToken
351);
352
353base64_newtype!(
354    /// PEM-encoded content (double-encoded in base64)
355    ///
356    /// This type represents PEM text that gets base64-encoded for JSON.
357    /// Used when APIs expect base64-encoded PEM strings.
358    PemContent
359);
360
361// ============================================================================
362// Identifier Types (String Wrappers for Semantic Clarity)
363// ============================================================================
364
365/// UUID for a Rekor log entry
366///
367/// This is the unique identifier for an entry in the transparency log.
368#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
369#[serde(transparent)]
370pub struct EntryUuid(String);
371
372impl EntryUuid {
373    pub fn new(s: String) -> Self {
374        EntryUuid(s)
375    }
376
377    pub fn as_str(&self) -> &str {
378        &self.0
379    }
380
381    pub fn into_string(self) -> String {
382        self.0
383    }
384
385    pub fn is_empty(&self) -> bool {
386        self.0.is_empty()
387    }
388}
389
390impl From<String> for EntryUuid {
391    fn from(s: String) -> Self {
392        EntryUuid::new(s)
393    }
394}
395
396impl AsRef<str> for EntryUuid {
397    fn as_ref(&self) -> &str {
398        &self.0
399    }
400}
401
402impl std::fmt::Display for EntryUuid {
403    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
404        write!(f, "{}", self.0)
405    }
406}
407
408/// Transparency log index
409///
410/// Represents a log index in the transparency log. Per the protobuf spec,
411/// this is an int64. For JSON serialization, we serialize as an integer but
412/// accept both integers and strings for backwards compatibility.
413#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
414pub struct LogIndex(i64);
415
416impl LogIndex {
417    pub fn new(index: i64) -> Self {
418        LogIndex(index)
419    }
420
421    pub fn value(&self) -> i64 {
422        self.0
423    }
424
425    pub fn as_u64(&self) -> Option<u64> {
426        if self.0 >= 0 {
427            Some(self.0 as u64)
428        } else {
429            None
430        }
431    }
432}
433
434impl From<i64> for LogIndex {
435    fn from(index: i64) -> Self {
436        LogIndex::new(index)
437    }
438}
439
440impl From<u64> for LogIndex {
441    fn from(index: u64) -> Self {
442        LogIndex::new(index as i64)
443    }
444}
445
446impl std::fmt::Display for LogIndex {
447    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
448        write!(f, "{}", self.0)
449    }
450}
451
452impl Serialize for LogIndex {
453    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
454    where
455        S: serde::Serializer,
456    {
457        // Serialize as string to match existing bundle format
458        serializer.serialize_str(&self.0.to_string())
459    }
460}
461
462impl<'de> Deserialize<'de> for LogIndex {
463    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
464    where
465        D: serde::Deserializer<'de>,
466    {
467        use serde::de::{self, Visitor};
468
469        struct LogIndexVisitor;
470
471        impl<'de> Visitor<'de> for LogIndexVisitor {
472            type Value = LogIndex;
473
474            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
475                formatter.write_str("an integer or string representing a log index")
476            }
477
478            fn visit_i64<E>(self, value: i64) -> std::result::Result<LogIndex, E>
479            where
480                E: de::Error,
481            {
482                Ok(LogIndex::new(value))
483            }
484
485            fn visit_u64<E>(self, value: u64) -> std::result::Result<LogIndex, E>
486            where
487                E: de::Error,
488            {
489                Ok(LogIndex::new(value as i64))
490            }
491
492            fn visit_str<E>(self, value: &str) -> std::result::Result<LogIndex, E>
493            where
494                E: de::Error,
495            {
496                value
497                    .parse::<i64>()
498                    .map(LogIndex::new)
499                    .map_err(|_| de::Error::custom(format!("invalid log index: {}", value)))
500            }
501        }
502
503        deserializer.deserialize_any(LogIndexVisitor)
504    }
505}
506
507/// Transparency log key ID
508///
509/// Base64-encoded identifier for a transparency log (typically SHA-256 of public key).
510#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
511#[serde(transparent)]
512pub struct LogKeyId(String);
513
514impl LogKeyId {
515    pub fn new(s: String) -> Self {
516        LogKeyId(s)
517    }
518
519    /// Create from raw bytes (will be base64-encoded)
520    pub fn from_bytes(bytes: &[u8]) -> Self {
521        LogKeyId(base64::engine::general_purpose::STANDARD.encode(bytes))
522    }
523
524    /// Decode to raw bytes
525    pub fn decode(&self) -> Result<Vec<u8>> {
526        base64::engine::general_purpose::STANDARD
527            .decode(&self.0)
528            .map_err(|e| Error::InvalidEncoding(format!("invalid base64 in log key id: {}", e)))
529    }
530
531    pub fn as_str(&self) -> &str {
532        &self.0
533    }
534
535    pub fn into_string(self) -> String {
536        self.0
537    }
538}
539
540impl From<String> for LogKeyId {
541    fn from(s: String) -> Self {
542        LogKeyId::new(s)
543    }
544}
545
546impl AsRef<str> for LogKeyId {
547    fn as_ref(&self) -> &str {
548        &self.0
549    }
550}
551
552impl std::fmt::Display for LogKeyId {
553    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
554        write!(f, "{}", self.0)
555    }
556}
557
558/// Key ID for signature key identification
559///
560/// Optional hint used in DSSE to identify which key was used for signing.
561#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
562#[serde(transparent)]
563pub struct KeyId(String);
564
565impl KeyId {
566    pub fn new(s: String) -> Self {
567        KeyId(s)
568    }
569
570    pub fn as_str(&self) -> &str {
571        &self.0
572    }
573
574    pub fn into_string(self) -> String {
575        self.0
576    }
577
578    pub fn is_empty(&self) -> bool {
579        self.0.is_empty()
580    }
581}
582
583impl From<String> for KeyId {
584    fn from(s: String) -> Self {
585        KeyId::new(s)
586    }
587}
588
589impl AsRef<str> for KeyId {
590    fn as_ref(&self) -> &str {
591        &self.0
592    }
593}
594
595impl std::fmt::Display for KeyId {
596    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
597        write!(f, "{}", self.0)
598    }
599}
600
601// ============================================================================
602// Key Hint Type (Fixed 4-byte Size)
603// ============================================================================
604
605/// Key hint for checkpoint signature identification (4 bytes)
606///
607/// The key hint is the first 4 bytes of SHA-256(public_key_der).
608/// It is used in signed notes/checkpoints to match signatures to public keys.
609#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
610#[serde(transparent)]
611pub struct KeyHint(#[serde(with = "base64_bytes_array4")] [u8; 4]);
612
613impl KeyHint {
614    /// Create a new key hint from a 4-byte array
615    pub fn new(bytes: [u8; 4]) -> Self {
616        KeyHint(bytes)
617    }
618
619    /// Create from a slice (must be exactly 4 bytes)
620    pub fn try_from_slice(slice: &[u8]) -> crate::error::Result<Self> {
621        if slice.len() != 4 {
622            return Err(crate::error::Error::Validation(format!(
623                "key hint must be exactly 4 bytes, got {}",
624                slice.len()
625            )));
626        }
627        let mut arr = [0u8; 4];
628        arr.copy_from_slice(slice);
629        Ok(KeyHint(arr))
630    }
631
632    /// Get the key hint as a byte slice
633    pub fn as_bytes(&self) -> &[u8; 4] {
634        &self.0
635    }
636
637    /// Get the key hint as a slice
638    pub fn as_slice(&self) -> &[u8] {
639        &self.0
640    }
641}
642
643impl From<[u8; 4]> for KeyHint {
644    fn from(bytes: [u8; 4]) -> Self {
645        KeyHint::new(bytes)
646    }
647}
648
649impl AsRef<[u8]> for KeyHint {
650    fn as_ref(&self) -> &[u8] {
651        &self.0
652    }
653}
654
655/// Serde helper for base64-encoded 4-byte arrays
656mod base64_bytes_array4 {
657    use base64::{engine::general_purpose::STANDARD, Engine};
658    use serde::{Deserialize, Deserializer, Serializer};
659
660    pub fn serialize<S>(bytes: &[u8; 4], serializer: S) -> Result<S::Ok, S::Error>
661    where
662        S: Serializer,
663    {
664        serializer.serialize_str(&STANDARD.encode(bytes))
665    }
666
667    pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 4], D::Error>
668    where
669        D: Deserializer<'de>,
670    {
671        let s = String::deserialize(deserializer)?;
672        let bytes = STANDARD
673            .decode(&s)
674            .map_err(|e| serde::de::Error::custom(format!("invalid base64: {}", e)))?;
675        if bytes.len() != 4 {
676            return Err(serde::de::Error::custom(format!(
677                "expected 4 bytes, got {}",
678                bytes.len()
679            )));
680        }
681        let mut arr = [0u8; 4];
682        arr.copy_from_slice(&bytes);
683        Ok(arr)
684    }
685}
686
687// ============================================================================
688// SHA-256 Hash Type (Fixed Size)
689// ============================================================================
690
691/// SHA-256 hash digest (32 bytes)
692///
693/// Fixed-size hash with compile-time size guarantees.
694/// Serializes as base64, deserializes from either hex (64 chars) or base64.
695#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
696pub struct Sha256Hash([u8; 32]);
697
698impl Sha256Hash {
699    pub fn from_bytes(bytes: [u8; 32]) -> Self {
700        Sha256Hash(bytes)
701    }
702
703    pub fn try_from_slice(bytes: &[u8]) -> Result<Self> {
704        if bytes.len() != 32 {
705            return Err(Error::InvalidEncoding(format!(
706                "SHA-256 hash must be 32 bytes, got {}",
707                bytes.len()
708            )));
709        }
710        let mut arr = [0u8; 32];
711        arr.copy_from_slice(bytes);
712        Ok(Sha256Hash(arr))
713    }
714
715    pub fn from_hex(hex_str: &str) -> Result<Self> {
716        let bytes = hex::decode(hex_str)
717            .map_err(|e| Error::InvalidEncoding(format!("invalid hex: {}", e)))?;
718        Self::try_from_slice(&bytes)
719    }
720
721    pub fn from_base64(s: &str) -> Result<Self> {
722        let bytes = base64::engine::general_purpose::STANDARD
723            .decode(s)
724            .map_err(|e| Error::InvalidEncoding(format!("invalid base64: {}", e)))?;
725        Self::try_from_slice(&bytes)
726    }
727
728    /// Parse from hex or base64 string (auto-detect format)
729    pub fn from_hex_or_base64(s: &str) -> Result<Self> {
730        if s.len() == 64 && s.chars().all(|c| c.is_ascii_hexdigit()) {
731            return Self::from_hex(s);
732        }
733        Self::from_base64(s)
734    }
735
736    pub fn to_hex(&self) -> String {
737        hex::encode(self.0)
738    }
739
740    pub fn to_base64(&self) -> String {
741        base64::engine::general_purpose::STANDARD.encode(self.0)
742    }
743
744    pub fn as_bytes(&self) -> &[u8; 32] {
745        &self.0
746    }
747
748    pub fn as_slice(&self) -> &[u8] {
749        &self.0
750    }
751}
752
753impl AsRef<[u8]> for Sha256Hash {
754    fn as_ref(&self) -> &[u8] {
755        &self.0
756    }
757}
758
759impl From<[u8; 32]> for Sha256Hash {
760    fn from(bytes: [u8; 32]) -> Self {
761        Sha256Hash(bytes)
762    }
763}
764
765impl serde::Serialize for Sha256Hash {
766    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
767    where
768        S: serde::Serializer,
769    {
770        serializer.serialize_str(&self.to_base64())
771    }
772}
773
774impl<'de> serde::Deserialize<'de> for Sha256Hash {
775    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
776    where
777        D: serde::Deserializer<'de>,
778    {
779        let s = String::deserialize(deserializer)?;
780        Sha256Hash::from_hex_or_base64(&s).map_err(serde::de::Error::custom)
781    }
782}
783
784// ============================================================================
785// Hex-Encoded Log ID (for Rekor V1 API compatibility)
786// ============================================================================
787
788/// Hex-encoded transparency log ID
789///
790/// The Rekor V1 API returns log IDs as hex-encoded strings.
791/// This type handles the hex encoding and can convert to base64 for bundles.
792#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
793#[serde(transparent)]
794pub struct HexLogId(String);
795
796impl HexLogId {
797    pub fn new(s: String) -> Self {
798        HexLogId(s)
799    }
800
801    /// Create from raw bytes (will be hex-encoded)
802    pub fn from_bytes(bytes: &[u8]) -> Self {
803        HexLogId(hex::encode(bytes))
804    }
805
806    /// Decode to raw bytes
807    pub fn decode(&self) -> Result<Vec<u8>> {
808        hex::decode(&self.0).map_err(|e| Error::InvalidEncoding(format!("invalid hex: {}", e)))
809    }
810
811    /// Convert to base64 encoding (for bundle format)
812    pub fn to_base64(&self) -> Result<String> {
813        let bytes = self.decode()?;
814        Ok(base64::engine::general_purpose::STANDARD.encode(&bytes))
815    }
816
817    pub fn as_str(&self) -> &str {
818        &self.0
819    }
820
821    pub fn into_string(self) -> String {
822        self.0
823    }
824}
825
826impl From<String> for HexLogId {
827    fn from(s: String) -> Self {
828        HexLogId::new(s)
829    }
830}
831
832impl AsRef<str> for HexLogId {
833    fn as_ref(&self) -> &str {
834        &self.0
835    }
836}
837
838impl std::fmt::Display for HexLogId {
839    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
840        write!(f, "{}", self.0)
841    }
842}
843
844// ============================================================================
845// Hex-Encoded Hash (for Rekor V1 API)
846// ============================================================================
847
848/// Hex-encoded hash value
849///
850/// Used in Rekor V1 API responses where hashes are hex-encoded.
851#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
852#[serde(transparent)]
853pub struct HexHash(String);
854
855impl HexHash {
856    pub fn new(s: String) -> Self {
857        HexHash(s)
858    }
859
860    pub fn from_bytes(bytes: &[u8]) -> Self {
861        HexHash(hex::encode(bytes))
862    }
863
864    pub fn decode(&self) -> Result<Vec<u8>> {
865        hex::decode(&self.0).map_err(|e| Error::InvalidEncoding(format!("invalid hex: {}", e)))
866    }
867
868    pub fn as_str(&self) -> &str {
869        &self.0
870    }
871
872    pub fn into_string(self) -> String {
873        self.0
874    }
875
876    /// Convert to Sha256Hash (validates length)
877    pub fn to_sha256(&self) -> Result<Sha256Hash> {
878        Sha256Hash::from_hex(&self.0)
879    }
880}
881
882impl From<String> for HexHash {
883    fn from(s: String) -> Self {
884        HexHash::new(s)
885    }
886}
887
888impl AsRef<str> for HexHash {
889    fn as_ref(&self) -> &str {
890        &self.0
891    }
892}
893
894impl std::fmt::Display for HexHash {
895    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
896        write!(f, "{}", self.0)
897    }
898}
899
900#[cfg(test)]
901mod tests {
902    use super::*;
903
904    #[test]
905    fn test_der_certificate_roundtrip() {
906        let cert = DerCertificate::from_bytes(b"fake cert data");
907        let json = serde_json::to_string(&cert).unwrap();
908        let decoded: DerCertificate = serde_json::from_str(&json).unwrap();
909        assert_eq!(cert, decoded);
910    }
911
912    #[test]
913    fn test_signature_bytes_roundtrip() {
914        let sig = SignatureBytes::from_bytes(b"fake signature");
915        let json = serde_json::to_string(&sig).unwrap();
916        let decoded: SignatureBytes = serde_json::from_str(&json).unwrap();
917        assert_eq!(sig, decoded);
918    }
919
920    #[test]
921    fn test_sha256_hash() {
922        let hash_hex = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
923        let hash = Sha256Hash::from_hex(hash_hex).unwrap();
924        assert_eq!(hash.to_hex(), hash_hex);
925
926        // Can also deserialize from hex
927        let json_hex = format!("\"{}\"", hash_hex);
928        let from_hex: Sha256Hash = serde_json::from_str(&json_hex).unwrap();
929        assert_eq!(hash, from_hex);
930    }
931
932    #[test]
933    fn test_hex_log_id() {
934        let bytes = vec![1, 2, 3, 4];
935        let log_id = HexLogId::from_bytes(&bytes);
936        assert_eq!(log_id.as_str(), "01020304");
937        assert_eq!(log_id.decode().unwrap(), bytes);
938        assert_eq!(log_id.to_base64().unwrap(), "AQIDBA==");
939    }
940
941    #[test]
942    fn test_log_key_id() {
943        let bytes = vec![1, 2, 3, 4];
944        let key_id = LogKeyId::from_bytes(&bytes);
945        assert_eq!(key_id.decode().unwrap(), bytes);
946    }
947
948    #[test]
949    fn test_certificate_from_pem() {
950        let pem = "-----BEGIN CERTIFICATE-----\nYWJjZA==\n-----END CERTIFICATE-----";
951        let cert = DerCertificate::from_pem(pem).unwrap();
952        assert_eq!(cert.as_bytes(), b"abcd");
953    }
954
955    #[test]
956    fn test_certificate_from_pem_wrong_type() {
957        let pem = "-----BEGIN PRIVATE KEY-----\nYWJjZA==\n-----END PRIVATE KEY-----";
958        let result = DerCertificate::from_pem(pem);
959        assert!(result.is_err());
960        assert!(result
961            .unwrap_err()
962            .to_string()
963            .contains("expected CERTIFICATE"));
964    }
965
966    #[test]
967    fn test_certificate_to_pem() {
968        let cert = DerCertificate::from_bytes(b"abcd");
969        let pem = cert.to_pem();
970        assert!(pem.contains("-----BEGIN CERTIFICATE-----"));
971        assert!(pem.contains("-----END CERTIFICATE-----"));
972
973        // Round-trip
974        let cert2 = DerCertificate::from_pem(&pem).unwrap();
975        assert_eq!(cert, cert2);
976    }
977
978    #[test]
979    fn test_public_key_from_pem() {
980        let pem = "-----BEGIN PUBLIC KEY-----\nYWJjZA==\n-----END PUBLIC KEY-----";
981        let key = DerPublicKey::from_pem(pem).unwrap();
982        assert_eq!(key.as_bytes(), b"abcd");
983    }
984
985    #[test]
986    fn test_public_key_from_pem_wrong_type() {
987        let pem = "-----BEGIN PRIVATE KEY-----\nYWJjZA==\n-----END PRIVATE KEY-----";
988        let result = DerPublicKey::from_pem(pem);
989        assert!(result.is_err());
990        assert!(result
991            .unwrap_err()
992            .to_string()
993            .contains("expected PUBLIC KEY"));
994    }
995
996    #[test]
997    fn test_public_key_to_pem() {
998        let key = DerPublicKey::from_bytes(b"abcd");
999        let pem = key.to_pem();
1000        assert!(pem.contains("-----BEGIN PUBLIC KEY-----"));
1001        assert!(pem.contains("-----END PUBLIC KEY-----"));
1002
1003        // Round-trip
1004        let key2 = DerPublicKey::from_pem(&pem).unwrap();
1005        assert_eq!(key, key2);
1006    }
1007}