runmat-execution-artifact 0.6.2

Canonical execution bundles and program artifacts for RunMat
Documentation
use hpke::{
    aead::AesGcm128,
    kdf::HkdfSha256,
    kem::{Kem as _, X25519HkdfSha256},
    setup_receiver, setup_sender, Deserializable, OpModeR, OpModeS, Serializable,
};

use super::{
    EncryptedArtifact, EncryptionContext, ExecutionEncryptionProvider, ExecutionHpkeSuite,
    ExecutionRecipientKey,
};
use crate::{ArtifactError, ArtifactResult};

const INFO: &[u8] = b"runmat-execution-artifact-hpke-v1";

#[derive(Clone)]
pub struct NativeExecutionPrivateKey(<X25519HkdfSha256 as hpke::Kem>::PrivateKey);

#[derive(Clone, Copy, Debug, Default)]
pub struct NativeExecutionEncryption;

impl NativeExecutionEncryption {
    pub fn generate_recipient(
        &self,
        fingerprint: impl Into<String>,
        valid_after_unix_millis: u64,
        valid_before_unix_millis: u64,
    ) -> ArtifactResult<(ExecutionRecipientKey, NativeExecutionPrivateKey)> {
        let (private, public) = X25519HkdfSha256::gen_keypair();
        let recipient = ExecutionRecipientKey {
            suite: ExecutionHpkeSuite::X25519HkdfSha256Aes128GcmV1,
            public_key: public.to_bytes().to_vec(),
            fingerprint: fingerprint.into(),
            valid_after_unix_millis,
            valid_before_unix_millis,
            custodian_uri: None,
        };
        recipient.validate()?;
        Ok((recipient, NativeExecutionPrivateKey(private)))
    }
}

impl ExecutionEncryptionProvider for NativeExecutionEncryption {
    type PrivateKey = NativeExecutionPrivateKey;

    fn seal(
        &self,
        recipient: &ExecutionRecipientKey,
        context: EncryptionContext,
        plaintext: &[u8],
    ) -> ArtifactResult<EncryptedArtifact> {
        recipient.validate()?;
        context.validate()?;
        if context.total_length != plaintext.len() as u64
            || context.object_digest != runmat_execution::Digest::sha256(plaintext)
        {
            return Err(ArtifactError::Identity(
                "encryption context does not identify the plaintext".into(),
            ));
        }
        let public = <X25519HkdfSha256 as hpke::Kem>::PublicKey::from_bytes(&recipient.public_key)
            .map_err(encryption)?;
        let (encapsulated, mut sender) =
            setup_sender::<AesGcm128, HkdfSha256, X25519HkdfSha256>(&OpModeS::Base, &public, INFO)
                .map_err(encryption)?;
        let aad = context.aad()?;
        let ciphertext = sender.seal(plaintext, &aad).map_err(encryption)?;
        Ok(EncryptedArtifact {
            schema_version: 1,
            suite: recipient.suite,
            context,
            encapsulated_key: encapsulated.to_bytes().to_vec(),
            ciphertext,
        })
    }

    fn open(
        &self,
        private_key: &Self::PrivateKey,
        artifact: &EncryptedArtifact,
    ) -> ArtifactResult<Vec<u8>> {
        if artifact.schema_version != 1
            || artifact.suite != ExecutionHpkeSuite::X25519HkdfSha256Aes128GcmV1
        {
            return Err(ArtifactError::Invalid(
                "unsupported encrypted execution artifact".into(),
            ));
        }
        let encapsulated =
            <X25519HkdfSha256 as hpke::Kem>::EncappedKey::from_bytes(&artifact.encapsulated_key)
                .map_err(encryption)?;
        let mut receiver = setup_receiver::<AesGcm128, HkdfSha256, X25519HkdfSha256>(
            &OpModeR::Base,
            &private_key.0,
            &encapsulated,
            INFO,
        )
        .map_err(encryption)?;
        let aad = artifact.context.aad()?;
        let plaintext = receiver
            .open(&artifact.ciphertext, &aad)
            .map_err(encryption)?;
        artifact.context.validate()?;
        if artifact.context.total_length != plaintext.len() as u64
            || artifact.context.object_digest != runmat_execution::Digest::sha256(&plaintext)
        {
            return Err(ArtifactError::Identity(
                "decrypted artifact does not match its authenticated identity".into(),
            ));
        }
        Ok(plaintext)
    }
}

fn encryption(error: impl std::fmt::Display) -> ArtifactError {
    ArtifactError::Encryption(error.to_string())
}