nstilt1 / crypto-on-the-edge

A WIP Rust crate for generating private keys from IDs based on an HKDF, and eliminates the need to store private keys.
Apache License 2.0
0 stars 0 forks source link

Impracticalities when automating versioning #1

Closed nstilt1 closed 5 months ago

nstilt1 commented 6 months ago

It is impractical to automate versioning, updating EPOCH times, and rotating HMAC keys with the current state of this library. The following challenges are easy to address:

Example Implementation

This little example would depend on the above change, but it might be a little impractical. It also might be better to use a seeded CSPRNG as the core and use the version number in the RNG's nonce.

pub struct MasterKey<K: CryptoKeyGenerator, const VERSION_LIFESPAN: u64, const MASTER_EPOCH: u64> {
    core: K,
    current_version: u16,
    current_epoch: u64,
    // the current version's key generator, primarily used for generating new IDs
    current_keygenerator: K
}

fn now() -> u64 {
    let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
}

impl<...> MasterKey<...> {
    fn get_current_version() -> u16 {
        (now() - MASTER_EPOCH) / VERSION_LIFESPAN
    }
    fn get_version_epoch(version: u16) -> u64 {
        MASTER_EPOCH + VERSION_LIFESPAN * version as u64
    }

    pub fn new(hmac_key: &[u8], app_id: &[u8]) -> Self {
        let version = Self::get_current_version();
        let current_epoch = get_version_epoch(version);
        let core: K::new(hmac_key, app_id);
        Self {
            core,
            current_version: version,
            current_epoch,
            // this method does not exist as of now, and it probably won't exist
            current_generator: core.generate_key_generator(version, current_epoch)
        }
    }

    /// using the current generator to make new IDs
    pub fn generate_some_id(&self) -> Id {
        self.current_generator.generate_some_id(...);
    }
    ...
}

While it may be in the realm of being possible, it would add an extra argument when using KeyGenerator to generate IDs directly, and it would increase the amount of computations required to generate a single ID of an arbitrary version, whether it is the current version or a version less than the current version.

It might be a bit inefficient when a given MasterKey is required to generate and validate IDs from different versions automatically because it does not seem simple to store non-current-version KeyGenerators without using alloc, which might limit the number of times the program needs to regenerate a Key Generator in a single session.

Then... let's say 5 versions down, your main signature algorithm or your hash function is found to have a critical vulnerability, and you need to switch either the signature algorithm or the hash function, or both. You would need a way to tell MasterKey that for versions 4 and below, it needs to use X algorithm, and use Y algorithm if the version is greater than 4. Switching signature algorithms would be a little bit easier since those key IDs can expire, but switching hash algorithms in the HMAC/HKDF would be a bit more challenging.

I'm not saying that the current state of this crate is ideal, but I am saying that completely automating crypto-system changes might be infeasible. This really depends upon your own threat model, and maybe some things are just meant to be a little bit more manual.

My question is: Is there a good way to automate HMAC key rotations using a regular version argument that would make it reasonable to change VERSION from a constant parameter to a non-const?

nstilt1 commented 5 months ago

This example implementation is a bit complex when a single instance of a Generator of sorts needs to generate IDs and keys from multiple versions. I will probably simplify the process to use a CSPRNG (likely ChaCha8) to output a "version nonce" that correlates to the version. This nonce will be used when generating MACs for the IDs, as well as for the private keys. It will allow for only one Mac instance to be active and only one Hkdf instance to be active. I have not decided on how long the nonce will be, but the OutputSize seems reasonable, but so does the length of ChaCha's output. The only problem with ChaCha's output size is that it varies by the backend. I will likely include this as a generic argument of type ArraySize.