keepass-ng 0.10.5

KeePass .kdbx database file parser with ehancements
Documentation
use crate::error::BlockStreamError;
#[cfg(feature = "save_kdbx4")]
use crate::error::CryptographyError;
use byteorder::{ByteOrder, LittleEndian};
use cipher::generic_array::{GenericArray, typenum::U64};
use hex_literal::hex;

pub const HMAC_KEY_END: [u8; 1] = hex!("01");

/// Read from a HMAC block stream into a raw buffer
pub(crate) fn read_hmac_block_stream(data: &[u8], key: &GenericArray<u8, U64>) -> Result<Vec<u8>, BlockStreamError> {
    // keepassxc src/streams/HmacBlockStream.cpp

    let mut out = Vec::new();

    let mut pos = 0;
    let mut block_index: u64 = 0;

    while pos < data.len() {
        let hmac = &data[pos..(pos + 32)];
        let size_bytes = &data[(pos + 32)..(pos + 36)];
        let size = LittleEndian::read_u32(size_bytes) as usize;
        let block = &data[(pos + 36)..(pos + 36 + size)];

        // verify block hmac
        let hmac_block_key = get_hmac_block_key(block_index, key);
        let mut block_index_buf = [0u8; 8];
        LittleEndian::write_u64(&mut block_index_buf, block_index);

        if hmac != crate::crypt::calculate_hmac(&[&block_index_buf, size_bytes, block], hmac_block_key.as_slice())?.as_slice() {
            return Err(BlockStreamError::BlockHashMismatch { block_index });
        }

        pos += 36 + size;
        block_index += 1;

        if size == 0 {
            break;
        }

        out.extend_from_slice(block);
    }

    Ok(out)
}

/// Write a raw buffer as a HMAC block stream
#[cfg(feature = "save_kdbx4")]
pub(crate) fn write_hmac_block_stream(data: &[u8], key: &GenericArray<u8, U64>) -> Result<Vec<u8>, CryptographyError> {
    let mut out = Vec::new();

    let mut pos = 0;
    let mut block_index = 0;

    while pos < data.len() {
        let size = data.len() - pos;

        let block = &data[pos..(pos + size)];

        let mut size_bytes: Vec<u8> = vec![0; 4];
        #[allow(clippy::cast_possible_truncation)]
        LittleEndian::write_u32(&mut size_bytes, size as u32);

        // Generate block hmac
        let hmac_block_key = get_hmac_block_key(block_index, key);
        let mut block_index_buf = [0u8; 8];
        LittleEndian::write_u64(&mut block_index_buf, block_index);

        let hmac = crate::crypt::calculate_hmac(&[&block_index_buf, &size_bytes, block], hmac_block_key.as_slice())?;

        pos += 36 + size;
        block_index += 1;

        out.extend_from_slice(hmac.as_slice());
        out.extend_from_slice(&size_bytes);
        out.extend_from_slice(block);
    }

    // the end of the HMAC block stream should be an empty block, but with a valid HMAC
    let hmac_block_key = get_hmac_block_key(block_index, key);
    let mut block_index_buf = [0u8; 8];
    LittleEndian::write_u64(&mut block_index_buf, block_index);

    let size_bytes = vec![0; 4];
    let hmac = crate::crypt::calculate_hmac(&[&block_index_buf, &size_bytes, &[]], hmac_block_key.as_slice())?;

    out.extend_from_slice(hmac.as_slice());
    out.extend_from_slice(&size_bytes);

    Ok(out)
}

pub(crate) fn get_hmac_block_key(block_index: u64, key: &GenericArray<u8, U64>) -> GenericArray<u8, U64> {
    let mut buf = [0u8; 8];
    LittleEndian::write_u64(&mut buf, block_index);
    crate::crypt::calculate_sha512(&[&buf, key])
}