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::CosignVerificationKey;
use crate::crypto::merkle::{MerkleProofVerifier, Rfc6269Default, Rfc6269HasherTrait};
use crate::errors::SigstoreError;
use crate::errors::SigstoreError::{InclusionProofError, UnexpectedError};
use crate::rekor::TreeSize;
use crate::rekor::models::checkpoint::SignedCheckpoint;
use sha2::Sha256;
use sha2::digest::Output;

use serde::{Deserialize, Serialize};

/// Stores the signature over the artifact's logID, logIndex, body and integratedTime.
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct InclusionProof {
    /// The index of the entry in the transparency log
    #[serde(rename = "logIndex")]
    pub log_index: i64,
    /// The hash value stored at the root of the merkle tree at the time the proof was generated
    #[serde(rename = "rootHash")]
    pub root_hash: [u8; 32],
    /// The size of the merkle tree at the time the inclusion proof was generated
    #[serde(rename = "treeSize")]
    pub tree_size: TreeSize,
    /// A list of hashes required to compute the inclusion proof, sorted in order from leaf to root
    #[serde(rename = "hashes")]
    pub hashes: Vec<[u8; 32]>,
    /// A snapshot of the transparency log's state at a specific point in time,
    /// in [Signed Note format].
    ///
    /// [Signed Note format]: https://github.com/transparency-dev/formats/blob/main/log/README.md
    pub checkpoint: Option<SignedCheckpoint>,
}

impl InclusionProof {
    pub fn new(
        log_index: i64,
        root_hash: [u8; 32],
        tree_size: TreeSize,
        hashes: Vec<[u8; 32]>,
        checkpoint: Option<SignedCheckpoint>,
    ) -> InclusionProof {
        InclusionProof {
            log_index,
            root_hash,
            tree_size,
            hashes,
            checkpoint,
        }
    }

    /// Verify that the canonically encoded `entry` is included in the log,
    /// and the included checkpoint was signed by the log.
    pub fn verify(
        &self,
        entry: &[u8],
        rekor_key: &CosignVerificationKey,
    ) -> Result<(), SigstoreError> {
        // enforce that there is a checkpoint
        let checkpoint = self.checkpoint.as_ref().ok_or(UnexpectedError(
            "inclusion proof misses checkpoint".to_string(),
        ))?;

        // verify the checkpoint signature
        checkpoint.verify_signature(rekor_key)?;

        // check if the inclusion and checkpoint match
        checkpoint.is_valid_for_proof(&self.root_hash.into(), self.tree_size)?;

        let entry_hash = Rfc6269Default::hash_leaf(entry);
        // convert hashes from bytestring and into Sha256
        let proof_hashes: Vec<Output<Sha256>> = self
            .hashes
            .iter()
            .map(|h| Output::<Sha256>::from(*h))
            .collect();

        Rfc6269Default::verify_inclusion(
            self.log_index as u64,
            &entry_hash,
            self.tree_size,
            &proof_hashes,
            &self.root_hash.into(),
        )
        .map_err(InclusionProofError)
    }
}