dat 4.7.0

DAT - Distributed Access Token
Documentation
use crate::error::DatError;
use crate::signature::{DatSignature, DatSignatureAlgorithm};
use aws_lc_rs::signature::{
    ECDSA_P256_SHA256_FIXED, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED,
    ECDSA_P384_SHA384_FIXED_SIGNING, ECDSA_P521_SHA512_FIXED, ECDSA_P521_SHA512_FIXED_SIGNING,
    EcdsaKeyPair, KeyPair, UnparsedPublicKey,
};

use crate::signature::DatSignature::Ecdsa;
use DatSignatureAlgorithm::*;

type OffsetPkcs8v1 = usize;
type PrivateLen = usize;
type PublicLen = usize;

fn ecdsa_info(
    algorithm: DatSignatureAlgorithm,
) -> Result<(OffsetPkcs8v1, PrivateLen, PublicLen), DatError> {
    Ok(match algorithm {
        EcdsaP256 => (36, 32, 65),
        EcdsaP384 => (35, 48, 97),
        EcdsaP521 => (35, 66, 133),
        _ => {
            return Err(DatError::ConfigAlgUnsupported(format!(
                "not an ecdsa signature algorithm: {algorithm}"
            )));
        }
    })
}

pub(crate) fn from_or_new_ecdsa(
    new: bool,
    algorithm: DatSignatureAlgorithm,
    key: &[u8],
) -> Result<DatSignature, DatError> {
    let (sa, va) = match algorithm {
        EcdsaP256 => &(ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P256_SHA256_FIXED),
        EcdsaP384 => &(ECDSA_P384_SHA384_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED),
        EcdsaP521 => &(ECDSA_P521_SHA512_FIXED_SIGNING, ECDSA_P521_SHA512_FIXED),
        _ => {
            return Err(DatError::ConfigAlgUnsupported(format!(
                "not an ecdsa signature algorithm: {algorithm}"
            )));
        }
    };
    let (_, private_len, public_len) = ecdsa_info(algorithm)?;

    let (key_pair, public_key) = if new {
        let key_pair = EcdsaKeyPair::generate(sa)
            .map_err(|_| DatError::InternalUnknown("ecdsa key generation failed"))?;
        let public_key = UnparsedPublicKey::new(va, Vec::from(key_pair.public_key().as_ref()));
        (Some(key_pair), public_key)
    } else if key.len() == private_len + public_len {
        let key_pair = EcdsaKeyPair::from_private_key_and_public_key(
            sa,
            &key[..private_len],
            &key[private_len..],
        )
        .map_err(|_| DatError::KeyInvalid("ecdsa private/public key material rejected"))?;
        let public_key = UnparsedPublicKey::new(va, Vec::from(key_pair.public_key().as_ref()));
        (Some(key_pair), public_key)
    } else if key.len() == public_len {
        (None, UnparsedPublicKey::new(va, Vec::from(key)))
    } else {
        return Err(DatError::KeyInvalid(
            "ecdsa key length matches neither private+public nor public",
        ));
    };

    Ok(Ecdsa(algorithm, key_pair, public_key))
}

pub(crate) fn export_key_ecdsa(
    algorithm: DatSignatureAlgorithm,
    key_pair: &Option<EcdsaKeyPair>,
    public_key: &UnparsedPublicKey<Vec<u8>>,
    verifying_only: bool,
) -> Result<Vec<u8>, DatError> {
    if !verifying_only && let Some(key_pair) = key_pair {
        let (offset_pkcs8v1, private_len, public_len) = ecdsa_info(algorithm)?;
        let mut key = Vec::with_capacity(public_len + private_len);
        let pkcs8v1 = key_pair
            .to_pkcs8v1()
            .map_err(|_| DatError::SigBackend("ecdsa pkcs8v1 export failed"))?;
        let pkcs8v1 = pkcs8v1.as_ref();
        let (private_key, public_key) =
            ecdsa_export_parts(pkcs8v1, offset_pkcs8v1, private_len, public_len)?;
        key.extend_from_slice(private_key);
        key.extend_from_slice(public_key);
        Ok(key)
    } else {
        Ok(public_key.as_ref().to_vec())
    }
}

fn ecdsa_export_parts(
    pkcs8v1: &[u8],
    private_offset: usize,
    private_len: usize,
    public_len: usize,
) -> Result<(&[u8], &[u8]), DatError> {
    let private_end = private_offset
        .checked_add(private_len)
        .ok_or(DatError::SigBackend("ecdsa pkcs8v1 layout is invalid"))?;
    let public_offset = pkcs8v1
        .len()
        .checked_sub(public_len)
        .ok_or(DatError::SigBackend("ecdsa pkcs8v1 layout is invalid"))?;
    let private_key = pkcs8v1
        .get(private_offset..private_end)
        .ok_or(DatError::SigBackend("ecdsa pkcs8v1 layout is invalid"))?;
    let public_key = pkcs8v1
        .get(public_offset..)
        .ok_or(DatError::SigBackend("ecdsa pkcs8v1 layout is invalid"))?;
    Ok((private_key, public_key))
}

pub(crate) fn sign_ecdsa(
    key_pair: &Option<EcdsaKeyPair>,
    data: &[u8],
) -> Result<Box<[u8]>, DatError> {
    let signature = key_pair
        .as_ref()
        .ok_or(DatError::SigKeyMissing)?
        .sign(&aws_lc_rs::rand::SystemRandom::new(), data)
        .map_err(|_| DatError::SigBackend("ecdsa sign failed"))?;
    Ok(Box::from(signature.as_ref()))
}

pub(crate) fn verify_ecdsa(
    public_key: &UnparsedPublicKey<Vec<u8>>,
    body: &[u8],
    sign: &[u8],
) -> Result<(), DatError> {
    public_key
        .verify(body, sign)
        .map_err(|_| DatError::SigMismatch)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn invalid_export_layout_returns_backend_error() {
        for result in [
            ecdsa_export_parts(&[0; 8], 7, 2, 1),
            ecdsa_export_parts(&[0; 8], usize::MAX, 2, 1),
            ecdsa_export_parts(&[0; 8], 0, 1, 9),
        ] {
            assert!(matches!(result, Err(DatError::SigBackend(_))));
        }
    }
}