ps-datachunk 0.1.0-37

Chunk-based data encrypter
Documentation
use bytes::Bytes;
use ps_cypher::DecryptionError;
use ps_hash::Hash;

use crate::{DataChunkError, OwnedDataChunk, Result};

pub fn decrypt(encrypted: impl AsRef<[u8]>, key: &Hash) -> Result<OwnedDataChunk> {
    let data = match ps_cypher::decrypt(encrypted.as_ref(), key) {
        Ok(buffer) => Bytes::from_owner(buffer),
        // match to ensure the key mismatch being removed fails compilation
        Err(DecryptionError::KeyMismatch) => Err(DataChunkError::HashMismatch)?,
        // misc failures are uninteresting and thus passed through
        Err(err) => Err(err)?,
    };

    // we're relying on ps-cypher's integrity check
    let chunk = OwnedDataChunk::from_parts_unchecked(data, *key);

    Ok(chunk)
}

#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
    use chacha20poly1305::aead::{Aead, KeyInit};
    use chacha20poly1305::ChaCha20Poly1305;

    use crate::DataChunkError;

    /// Number of ECC parity bytes per codeword, matching `ps_cypher::encrypt`.
    const PARITY: u8 = 12;

    /// A ciphertext sealed under key `K` by an attacker who knows `K` must be
    /// rejected: its plaintext does not hash to `K`, so accepting it would
    /// yield a chunk whose content does not match its address.
    #[test]
    fn decrypt_rejects_forged_ciphertext() {
        let legitimate = b"legitimate chunk data";
        let forged_plaintext = b"forged malicious data";

        let key = ps_hash::hash(legitimate).expect("hashing should succeed");

        // Seal the foreign plaintext with the key and nonce derived from `key`,
        // mirroring ps-cypher's encryption pipeline.
        let compressed =
            ps_compress::compress(forged_plaintext).expect("compression should succeed");

        let [_, _, nonce @ ..] = *key.parity();
        let encryption_key = *key.digest();

        let chacha = ChaCha20Poly1305::new(&encryption_key.into());
        let sealed = chacha
            .encrypt(&nonce.into(), compressed.as_ref())
            .expect("sealing should succeed");
        let forged = ps_ecc::encode(&sealed, PARITY).expect("ECC encoding should succeed");

        let result = super::decrypt(&forged, &key);

        assert!(matches!(result, Err(DataChunkError::HashMismatch)));
    }
}