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

Code before:
use rng256::Rng256;
use sk_cbor as cbor;
use sk_cbor::{cbor_array, cbor_bytes, cbor_int};
#[cfg(feature = "ed25519")]
use ed25519_dalek::Signer;

// Legacy credential IDs consist of
// - 16 bytes: initialization vector for AES-256,
// - 32 bytes: ECDSA private key for the credential,
// - 32 bytes: relying party ID hashed with SHA256,
// - 32 bytes: HMAC-SHA256 over everything else.
pub const LEGACY_CREDENTIAL_ID_SIZE: usize = 112;
#[cfg(test)]
pub const ECDSA_CREDENTIAL_ID_SIZE: usize = 113;
// See encrypt_key_handle v1 documentation.
pub const MAX_CREDENTIAL_ID_SIZE: usize = 113;

const ECDSA_CREDENTIAL_ID_VERSION: u8 = 0x01;
#[cfg(feature = "ed25519")]
const ED25519_CREDENTIAL_ID_VERSION: u8 = 0x02;
#[cfg(test)]
#[cfg(feature = "ed25519")]
const UNSUPPORTED_CREDENTIAL_ID_VERSION: u8 = 0x03;
#[cfg(test)]
#[cfg(not(feature = "ed25519"))]
const UNSUPPORTED_CREDENTIAL_ID_VERSION: u8 = 0x02;

/// Wraps the AES256-CBC encryption to match what we need in CTAP.
pub fn aes256_cbc_encrypt(
    rng: &mut dyn Rng256,
    aes_enc_key: &crypto::aes256::EncryptionKey,
    plaintext: &[u8],
    embeds_iv: bool,
) -> Result<Vec<u8>, Ctap2StatusCode> {
    if plaintext.len() % 16 != 0 {
        return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
    }
    // The extra 1 capacity is because encrypt_key_handle adds a version number.
    let mut ciphertext = Vec::with_capacity(plaintext.len() + 16 * embeds_iv as usize + 1);
    let iv = if embeds_iv {
        let random_bytes = rng.gen_uniform_u8x32();
        ciphertext.extend_from_slice(&random_bytes[..16]);
        *array_ref!(ciphertext, 0, 16)
    } else {
        [0u8; 16]
    };
    let start = ciphertext.len();
    ciphertext.extend_from_slice(plaintext);
    cbc_encrypt(aes_enc_key, iv, &mut ciphertext[start..]);
    Ok(ciphertext)
}

Review comment (suggestion):
@kaczmarczyck Is there a reason why we need the unsupported version to be the next available one? Compared to `0xff` for example.

Code after:
use crypto::hmac::{hmac_256, verify_hmac_256};
use crypto::sha256::Sha256;
use rng256::Rng256;
use sk_cbor as cbor;
use sk_cbor::{cbor_array, cbor_bytes, cbor_int};

// Legacy credential IDs consist of
// - 16 bytes: initialization vector for AES-256,
// - 32 bytes: ECDSA private key for the credential,
// - 32 bytes: relying party ID hashed with SHA256,
// - 32 bytes: HMAC-SHA256 over everything else.
pub const LEGACY_CREDENTIAL_ID_SIZE: usize = 112;
#[cfg(test)]
pub const ECDSA_CREDENTIAL_ID_SIZE: usize = 113;
// See encrypt_key_handle v1 documentation.
pub const MAX_CREDENTIAL_ID_SIZE: usize = 113;

const ECDSA_CREDENTIAL_ID_VERSION: u8 = 0x01;
#[allow(dead_code)]
const ED25519_CREDENTIAL_ID_VERSION: u8 = 0x02;
#[cfg(test)]
const UNSUPPORTED_CREDENTIAL_ID_VERSION: u8 = 0x80;

/// Wraps the AES256-CBC encryption to match what we need in CTAP.
pub fn aes256_cbc_encrypt(
    rng: &mut dyn Rng256,
    aes_enc_key: &crypto::aes256::EncryptionKey,
    plaintext: &[u8],
    embeds_iv: bool,
) -> Result<Vec<u8>, Ctap2StatusCode> {
    if plaintext.len() % 16 != 0 {
        return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
    }
    // The extra 1 capacity is because encrypt_key_handle adds a version number.
    let mut ciphertext = Vec::with_capacity(plaintext.len() + 16 * embeds_iv as usize + 1);
    let iv = if embeds_iv {
        let random_bytes = rng.gen_uniform_u8x32();
        ciphertext.extend_from_slice(&random_bytes[..16]);
        *array_ref!(ciphertext, 0, 16)
    } else {
        [0u8; 16]
    };
    let start = ciphertext.len();
    ciphertext.extend_from_slice(plaintext);
    cbc_encrypt(aes_enc_key, iv, &mut ciphertext[start..]);
    Ok(ciphertext)
}

/// Wraps the AES256-CBC decryption to match what we need in CTAP.
pub fn aes256_cbc_decrypt(
    aes_enc_key: &crypto::aes256::EncryptionKey,