hdiff-update-core 0.1.4

Core library for signed HDiffPatch-based differential application updates.
Documentation
use base64::{engine::general_purpose::STANDARD, Engine};
use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey};
use serde::{Deserialize, Serialize};

use crate::{Error, ManifestSignature, Result, UpdateManifest, MANIFEST_SIGNATURE_ALGORITHM};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateKeypair {
    pub private_key: String,
    pub public_key: String,
    pub key_id: String,
}

pub fn generate_update_keypair() -> Result<UpdateKeypair> {
    let mut secret = [0_u8; 32];
    getrandom::fill(&mut secret).map_err(|error| Error::InvalidKeyMaterial {
        kind: "random seed".to_string(),
        message: error.to_string(),
    })?;
    let signing_key = SigningKey::from_bytes(&secret);
    let public_key = signing_key.verifying_key().to_bytes();

    Ok(UpdateKeypair {
        private_key: STANDARD.encode(secret),
        public_key: STANDARD.encode(public_key),
        key_id: key_id(&public_key),
    })
}

pub fn sign_manifest(
    manifest: &mut UpdateManifest,
    private_key: &str,
    signature_key_id: Option<String>,
) -> Result<ManifestSignature> {
    let signing_key = signing_key_from_base64(private_key)?;
    let signature: Signature = signing_key.sign(&manifest.signing_payload()?);
    let signing_key_id =
        signature_key_id.unwrap_or_else(|| key_id(&signing_key.verifying_key().to_bytes()));
    let signature = ManifestSignature {
        algorithm: MANIFEST_SIGNATURE_ALGORITHM.to_string(),
        key_id: Some(signing_key_id),
        value: STANDARD.encode(signature.to_bytes()),
    };
    manifest.signature = Some(signature.clone());
    Ok(signature)
}

pub fn verify_manifest_signature(manifest: &UpdateManifest, public_key: &str) -> Result<()> {
    let signature = manifest.signature.as_ref().ok_or(Error::SignatureMissing)?;
    if signature.algorithm != MANIFEST_SIGNATURE_ALGORITHM {
        return Err(Error::UnsupportedAlgorithm(signature.algorithm.clone()));
    }

    let verifying_key = verifying_key_from_base64(public_key)?;
    let signature_bytes = decode_exact::<64>(&signature.value, "manifest signature")?;
    let signature = Signature::from_bytes(&signature_bytes);
    verifying_key
        .verify_strict(&manifest.signing_payload()?, &signature)
        .map_err(|_| Error::SignatureVerificationFailed)
}

pub fn key_id(public_key: &[u8; 32]) -> String {
    let digest = crate::sha256_bytes(public_key);
    digest[..16].to_string()
}

fn signing_key_from_base64(private_key: &str) -> Result<SigningKey> {
    let bytes = decode_exact::<32>(private_key, "private key")?;
    Ok(SigningKey::from_bytes(&bytes))
}

fn verifying_key_from_base64(public_key: &str) -> Result<VerifyingKey> {
    let bytes = decode_exact::<32>(public_key, "public key")?;
    VerifyingKey::from_bytes(&bytes).map_err(|error| Error::InvalidKeyMaterial {
        kind: "public key".to_string(),
        message: error.to_string(),
    })
}

fn decode_exact<const N: usize>(value: &str, kind: &str) -> Result<[u8; N]> {
    let bytes = STANDARD
        .decode(value.trim())
        .map_err(|error| Error::InvalidKeyMaterial {
            kind: kind.to_string(),
            message: error.to_string(),
        })?;
    bytes
        .try_into()
        .map_err(|bytes: Vec<u8>| Error::InvalidKeyMaterial {
            kind: kind.to_string(),
            message: format!("expected {N} bytes, got {}", bytes.len()),
        })
}