llmix-rs 2.0.6

Rust binding for the LLMix orchestration contract with cache, resilience, and config parity
Documentation
use super::fs_ops::{canonical_json_bytes, sha256_bytes, validate_manifest_entry_string};
use super::*;

pub(super) fn build_registry_root_payload(
    manifest: &RegistryManifest,
    manifest_sha256: &str,
) -> LlmixResult<RegistryRootPayload> {
    validate_sha256(manifest_sha256, "registry root manifest")?;
    let current_pointer = CurrentPointer {
        revision: manifest.revision.clone(),
        manifest_sha256: Some(manifest_sha256.to_string()),
    };
    let current_sha256 = sha256_bytes(&current_pointer_bytes(&current_pointer)?);

    Ok(RegistryRootPayload {
        schema: REGISTRY_ROOT_SCHEMA.to_string(),
        schema_version: REGISTRY_ROOT_SCHEMA_VERSION,
        revision: manifest.revision.clone(),
        published_at: manifest.published_at.clone(),
        current: RegistryRootCurrentBinding {
            path: "current.json".to_string(),
            revision: manifest.revision.clone(),
            manifest_sha256: manifest_sha256.to_string(),
            sha256: current_sha256,
        },
        manifest: RegistryRootManifestBinding {
            path: compiled_registry_path(&manifest.revision, "manifest.json"),
            sha256: manifest_sha256.to_string(),
        },
        files: registry_root_file_digests(manifest)?,
    })
}

pub(super) fn create_registry_root_envelope(
    payload: RegistryRootPayload,
    options: &RegistryRootSigningOptions<'_>,
) -> LlmixResult<RegistryRootEnvelope> {
    let signing_input = registry_root_signing_input(payload)?;
    let signatures = options.signer.sign_registry_root(&signing_input)?;
    let required = required_signature_count(options.min_signatures)?;
    if signatures.len() < required {
        return Err(InvalidConfigError {
            message: format!(
                "Registry root signer returned {} signatures; expected at least {required}",
                signatures.len()
            ),
        }
        .into());
    }
    for signature in &signatures {
        validate_registry_root_signature(signature)?;
        if signature.payload_digest != signing_input.integrity.digest {
            return Err(InvalidConfigError {
                message: "Registry root signature payload-digest mismatch".to_string(),
            }
            .into());
        }
        if signature.payload_type.as_deref() != Some(REGISTRY_ROOT_PAYLOAD_TYPE) {
            return Err(InvalidConfigError {
                message: "Registry root signature payload-type mismatch".to_string(),
            }
            .into());
        }
    }

    Ok(RegistryRootEnvelope {
        schema: REGISTRY_ROOT_ENVELOPE_SCHEMA.to_string(),
        schema_version: REGISTRY_ROOT_ENVELOPE_SCHEMA_VERSION,
        payload: signing_input.payload,
        integrity: signing_input.integrity,
        payload_sha256: signing_input.payload_sha256,
        signatures,
    })
}

pub(super) fn registry_root_signing_input(
    payload: RegistryRootPayload,
) -> LlmixResult<RegistryRootSigningInput> {
    let canonical_payload = canonical_registry_root_payload(&payload)?;
    let payload_sha256 = sha256_bytes(canonical_payload.as_bytes());
    let integrity = IntegrityField {
        algorithm: HashAlgorithm::Sha256,
        digest: format!("sha256:{payload_sha256}"),
    };
    Ok(RegistryRootSigningInput {
        payload,
        canonical_payload,
        integrity,
        payload_type: REGISTRY_ROOT_PAYLOAD_TYPE.to_string(),
        payload_sha256,
    })
}

pub(super) fn canonical_registry_root_payload(
    payload: &RegistryRootPayload,
) -> LlmixResult<String> {
    canonical_json::to_string(&serde_json::to_value(payload).map_err(LlmixError::from)?)
}

pub(super) fn registry_root_file_digests(
    manifest: &RegistryManifest,
) -> LlmixResult<Vec<RegistryRootFileDigest>> {
    let mut files = Vec::new();
    for entry in manifest.presets.values() {
        validate_manifest_entry_string(&entry.source_path, "source_path", "<registry-root>")?;
        validate_manifest_entry_string(&entry.source_sha256, "source_sha256", "<registry-root>")?;
        validate_manifest_entry_string(&entry.resolved_path, "resolved_path", "<registry-root>")?;
        validate_manifest_entry_string(
            &entry.resolved_sha256,
            "resolved_sha256",
            "<registry-root>",
        )?;
        validate_sha256(&entry.source_sha256, "registry root source file")?;
        validate_sha256(&entry.resolved_sha256, "registry root resolved file")?;
        files.push(RegistryRootFileDigest {
            path: compiled_registry_path(&manifest.revision, &entry.source_path),
            sha256: entry.source_sha256.clone(),
            role: "source".to_string(),
        });
        files.push(RegistryRootFileDigest {
            path: compiled_registry_path(&manifest.revision, &entry.resolved_path),
            sha256: entry.resolved_sha256.clone(),
            role: "resolved".to_string(),
        });
    }
    files.sort_by(|left, right| {
        left.path
            .cmp(&right.path)
            .then(left.role.cmp(&right.role))
            .then(left.sha256.cmp(&right.sha256))
    });
    Ok(files)
}

pub(super) fn validate_registry_root_signature(
    signature: &RegistryRootSignature,
) -> LlmixResult<()> {
    if signature.signer.is_empty()
        || signature.key_id.is_empty()
        || signature.signature.is_empty()
        || signature.payload_digest.is_empty()
    {
        return Err(InvalidConfigError {
            message: "Registry root signature is missing required fields".to_string(),
        }
        .into());
    }
    if !matches!(
        signature.algorithm.as_str(),
        "ed25519" | "ecdsa-p256" | "rsa-pss-sha256"
    ) {
        return Err(InvalidConfigError {
            message: "Registry root signature has unsupported algorithm".to_string(),
        }
        .into());
    }
    if signature.payload_type.as_deref() != Some(REGISTRY_ROOT_PAYLOAD_TYPE) {
        return Err(InvalidConfigError {
            message: "Registry root signature payload-type mismatch".to_string(),
        }
        .into());
    }
    Ok(())
}

pub(super) fn registry_root_signature_to_mda(signature: &RegistryRootSignature) -> SignatureEntry {
    SignatureEntry {
        signer: signature.signer.clone(),
        key_id: signature.key_id.clone(),
        payload_digest: signature.payload_digest.clone(),
        algorithm: signature.algorithm.clone(),
        signature: signature.signature.clone(),
        rekor_log_id: signature.rekor_log_id.clone(),
        rekor_log_index: signature.rekor_log_index,
        payload_type: signature.payload_type.clone(),
    }
}

pub(super) fn sort_registry_root_files(files: &mut [RegistryRootFileDigest]) {
    files.sort_by(|left, right| {
        left.path
            .cmp(&right.path)
            .then(left.role.cmp(&right.role))
            .then(left.sha256.cmp(&right.sha256))
    });
}

pub(super) fn current_pointer_bytes(pointer: &CurrentPointer) -> LlmixResult<Vec<u8>> {
    let value = serde_json::to_value(pointer).map_err(LlmixError::from)?;
    canonical_json_bytes(&value)
}

pub(super) fn compiled_registry_path(revision: &str, relative_path: &str) -> String {
    format!("compiled/{revision}/{relative_path}")
}

pub(super) fn compiled_relative_path(revision: &str, registry_path: &str) -> LlmixResult<String> {
    let prefix = format!("compiled/{revision}/");
    registry_path
        .strip_prefix(&prefix)
        .map(str::to_string)
        .ok_or_else(|| {
            SecurityError {
                message: format!(
                    "Registry root file path is outside active compiled revision: {registry_path}"
                ),
            }
            .into()
        })
}

pub(super) fn validate_sha256(value: &str, label: &str) -> LlmixResult<()> {
    if value.len() == 64 && value.bytes().all(|byte| byte.is_ascii_hexdigit()) {
        return Ok(());
    }
    Err(InvalidConfigError {
        message: format!("{label} has invalid sha256 digest"),
    }
    .into())
}

pub(super) fn normalize_sha256_digest(value: &str, label: &str) -> LlmixResult<String> {
    let normalized = value
        .strip_prefix("sha256:")
        .unwrap_or(value)
        .to_ascii_lowercase();
    validate_sha256(&normalized, label)?;
    Ok(normalized)
}

pub(super) fn required_signature_count(value: Option<usize>) -> LlmixResult<usize> {
    match value {
        Some(0) => Err(InvalidConfigError {
            message: "Registry root min_signatures must be >= 1".to_string(),
        }
        .into()),
        Some(count) => Ok(count),
        None => Ok(1),
    }
}