sigstore 0.14.0

An experimental crate to interact with sigstore
Documentation
/*
 * Rekor
 *
 * Rekor is a cryptographically secure, immutable transparency log for signed software releases.
 *
 * The version of the OpenAPI document: 0.0.1
 *
 * Generated by: https://openapi-generator.tech
 */

use crate::crypto::merkle::{MerkleProofVerifier, Rfc6269Default, hex_to_hash_output};
use crate::errors::SigstoreError;
use crate::errors::SigstoreError::ConsistencyProofError;

use serde::{Deserialize, Serialize};
use sha2::Sha256;
use sha2::digest::Output;

/// Used to deserialize responses of the Rekor API.
#[derive(Clone, Serialize, Deserialize)]
pub struct RekorConsistencyProof {
    /// The hash value in hex form stored at the root of the merkle tree at the time the proof was
    /// generated
    #[serde(rename = "rootHash")]
    pub root_hash: String,
    // the hashes stored in hex form
    #[serde(rename = "hashes")]
    pub hashes: Vec<String>,
}

/// Represents a Merkle consistency proof for a transparency log.
///
/// This struct is typically constructed from the log's API response in [`RekorConsistencyProof`].
///
/// It contains the hashes (as bytestring arrays) required to prove that a newer Merkle tree is an
/// append-only extension of a previous tree, as well as the root hash of the tree at the time the
/// proof was generated. This is used to verify the append-only property of the log between two
/// tree sizes.
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct ConsistencyProof {
    /// The hash value, in bytestring, stored at the root of the merkle tree at the time the proof
    /// was generated
    pub root_hash: [u8; 32],
    // the hashes stored in bytestring form
    pub hashes: Vec<[u8; 32]>,
}

impl TryFrom<RekorConsistencyProof> for ConsistencyProof {
    type Error = crate::errors::SigstoreError;

    fn try_from(raw: RekorConsistencyProof) -> Result<Self, Self::Error> {
        // let root_hash = <[u8; 32]>::from_hex(&raw.root_hash)?;
        let root_hash = hex_to_hash_output(&raw.root_hash)?.into();
        let hashes = raw
            .hashes
            .into_iter()
            .map(hex_to_hash_output)
            .map(|r| r.map(Into::into))
            .collect::<Result<_, _>>()?;
        Ok(ConsistencyProof { root_hash, hashes })
    }
}

impl ConsistencyProof {
    pub fn new(root_hash: [u8; 32], hashes: Vec<[u8; 32]>) -> ConsistencyProof {
        ConsistencyProof { root_hash, hashes }
    }

    /// Verify this consistency proof against the given parameters.
    /// If `new_root` is `Some` then this root will be used in the verification. If it is `None`
    /// then the root in `self.root_hash` is used.
    pub fn verify(
        &self,
        old_size: u64,
        old_root: &[u8; 32],
        new_size: u64,
        new_root: Option<&[u8; 32]>,
    ) -> Result<(), SigstoreError> {
        // convert hashes from bytestring and into Sha256
        let proof_hashes: Vec<Output<Sha256>> = self
            .hashes
            .iter()
            .map(|h| Output::<Sha256>::from(*h))
            .collect();

        let new_root = match new_root {
            Some(s) => s,
            None => &self.root_hash,
        };

        Rfc6269Default::verify_consistency(
            old_size,
            new_size,
            &proof_hashes,
            old_root.into(),
            new_root.into(),
        )
        .map_err(ConsistencyProofError)
    }
}