Repository: google/OpenSK
File: src/ctap/mod.rs (Rust)

Code before:
pub fn cbor_read(encoded_cbor: &[u8]) -> Result<cbor::Value, Ctap2StatusCode> {
    cbor::reader::read_nested(encoded_cbor, Some(MAX_CBOR_NESTING_DEPTH))
        .map_err(|_e| Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR)
}

fn cbor_write(value: cbor::Value, encoded_cbor: &mut Vec<u8>) -> Result<(), Ctap2StatusCode> {
    cbor::writer::write_nested(value, encoded_cbor, Some(MAX_CBOR_NESTING_DEPTH))
        .map_err(|_e| Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)
}

// This function is adapted from https://doc.rust-lang.org/nightly/src/core/str/mod.rs.html#2110
// (as of 2020-01-20) and truncates to "max" bytes, not breaking the encoding.
// We change the return value, since we don't need the bool.
fn truncate_to_char_boundary(s: &str, mut max: usize) -> &str {
    if max >= s.len() {
        s
    } else {
        while !s.is_char_boundary(max) {
            max -= 1;
        }
        &s[..max]
    }
}

/// Parses the metadata of an upgrade, and checks its correctness.
///
/// Returns the hash over the upgrade, including partition and some metadata.
/// The metadata consists of:
/// - 32B upgrade hash (SHA256)
/// -  4B timestamp (little endian encoding)
/// -  4B partition address (little endian encoding)
/// The upgrade hash is computed over the firmware image and all metadata,
/// except the hash itself.
fn parse_metadata(
    upgrade_locations: &impl UpgradeStorage,
    metadata: &[u8],
) -> Result<[u8; 32], Ctap2StatusCode> {
    const METADATA_LEN: usize = 40;
    if metadata.len() != METADATA_LEN {
        return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
    }
    // The hash implementation handles this in chunks, so no memory issues.
    let partition_slice = upgrade_locations
        .read_partition(0, upgrade_locations.partition_length())
        .map_err(|_| Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
    let mut hasher = Sha256::new();
    hasher.update(partition_slice);
    hasher.update(&metadata[32..METADATA_LEN]);
    let computed_hash = hasher.finalize();
    if &computed_hash != array_ref!(metadata, 0, 32) {
        return Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE);

Review comment (refactor):
By the way, this is specific to Nordic. A page is not necessarily 4kB. It makes sense for the bootloader which is Nordic-specific, but ctap-code should ideally be abstract over a page size. But I guess it's fine to keep it like that or rename "page" to "slice" or something.

Code after:
    cbor::reader::read_nested(encoded_cbor, Some(MAX_CBOR_NESTING_DEPTH))
        .map_err(|_e| Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR)
}

fn cbor_write(value: cbor::Value, encoded_cbor: &mut Vec<u8>) -> Result<(), Ctap2StatusCode> {
    cbor::writer::write_nested(value, encoded_cbor, Some(MAX_CBOR_NESTING_DEPTH))
        .map_err(|_e| Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)
}

// This function is adapted from https://doc.rust-lang.org/nightly/src/core/str/mod.rs.html#2110
// (as of 2020-01-20) and truncates to "max" bytes, not breaking the encoding.
// We change the return value, since we don't need the bool.
fn truncate_to_char_boundary(s: &str, mut max: usize) -> &str {
    if max >= s.len() {
        s
    } else {
        while !s.is_char_boundary(max) {
            max -= 1;
        }
        &s[..max]
    }
}

/// Parses the metadata of an upgrade, and checks its correctness.
///
/// The metadata is a page starting with:
/// - 32 B upgrade hash (SHA256)
/// - 64 B signature,
/// that are not signed over. The second part is included in the signature with
/// -  8 B version and
/// -  4 B partition address in little endian encoding
/// written at METADATA_SIGN_OFFSET.
///
/// Checks hash and signature correctness, and whether the partition offset matches.
fn parse_metadata(
    upgrade_locations: &impl UpgradeStorage,
    public_key_bytes: &[u8],
    metadata: &[u8],
) -> Result<(), Ctap2StatusCode> {
    const METADATA_LEN: usize = 0x1000;
    const METADATA_SIGN_OFFSET: usize = 0x800;
    if metadata.len() != METADATA_LEN {
        return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
    }

    let metadata_address = LittleEndian::read_u32(&metadata[METADATA_SIGN_OFFSET + 8..][..4]);
    if metadata_address as usize != upgrade_locations.partition_address() {
        return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
    }

    // The hash implementation handles this in chunks, so no memory issues.