Search code examples
rusterror-handling

TryInto<GenericArray> without panicking


I am using the chacha20poly1305 crate, which uses a GenericArray v0.14.7 for its Key. I now need to convert that key from a Vec<u8>. However GenericArray::from::<&[u8]>() is implemented to panic upon conversion errors (and thus also will try_from()). I would, however, like to convert from Vec<u8> to Key without panicking and handling errors gracefully.

Is there a way to do this without going the long way and converting into [u8; SIZE] fist? If not, is it possible to get the key's size from Key somehow without using a magic number in my code?

My current attempt is:

use clap::Parser;
use clap_stdin::FileOrStdin;
use ios_log_decrypt::EncryptedLog;
use log::error;
use rpassword::prompt_password;
use std::io::{stdout, Write};
use std::process::exit;

const KEY_SIZE: usize = 32;

#[derive(Debug, Parser)]
struct Args {
    #[arg(index = 1, help = "path to the encrypted log file")]
    logfile: FileOrStdin,
    #[arg(long, short, help = "hexadecimal decryption key")]
    key: Option<String>,
}

fn main() {
    env_logger::init();

    let args = Args::parse();
    let encrypted_log = EncryptedLog::new(args.logfile.to_string());
    let key: [u8; KEY_SIZE] = hex::decode(args.key.unwrap_or_else(|| {
        prompt_password("Decryption key: ").unwrap_or_else(|error| {
            error!("{error}");
            exit(1)
        })
    }))
    .unwrap_or_else(|error| {
        error!("{error}");
        exit(2);
    })
    .try_into()
    .unwrap_or_else(|vec: Vec<u8>| {
        error!("Invalid key size: {}", vec.len());
        exit(3);
    });

    for block in encrypted_log.decrypt(&key.into()) {
        match block {
            Ok(ref bytes) => stdout().write_all(bytes).expect("could not write bytes"),
            Err(error) => error!("{error}"),
        }
    }
}

Which is pretty ugly, since I need to hard-code the key size of 32 in my code and thus it is not directly tied to the size of Key. While the size of Key is unlikely to change in the future, possible discrepancies between Key's size and KEY_SIZE will lead the program to panic again.


Solution

  • You can create a conversion function:

    use generic_array::{ArrayLength, GenericArray};
    
    fn generic_array_try_from_slice<T, N>(data: &[T]) -> Option<&GenericArray<T, N>>
    where
        N: ArrayLength<T>,
    {
        if data.len() == N::to_usize() {
            Some(data.into())
        } else {
            None
        }
    }