rc-crypto 0.1.2

Crypto library for the RC X509 platform
Documentation
// Copyright 2026-Present Datadog, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

const MAX_LEN: usize = 120;

/// A signature generated by a [`PrivateKey`].
///
/// # Encoding
///
/// These signatures are, variable-length and utilise SHA256 as the internal
/// cryptographic hash.
///
/// The returned signatures are encoded as ASN.1 wrapped DER bytes as described
/// in [RFC 3279 § 2.2.3].
///
/// [`PrivateKey`]: crate::keys::PrivateKey
/// [RFC 3279 § 2.2.3]: https://tools.ietf.org/html/rfc3279#section-2.2.3
#[derive(Clone, PartialEq)]
pub struct Signature {
    data: [u8; MAX_LEN],
    len: u8,
}

impl std::fmt::Display for Signature {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.as_hex_str(f)
    }
}

impl std::fmt::Debug for Signature {
    fn fmt(&self, mut f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Signature(")?;
        self.as_hex_str(&mut f)?;
        write!(f, ")")
    }
}

impl Signature {
    /// Write this signature into `buf` formatted as a hex string.
    pub fn as_hex_str<W>(&self, mut buf: W) -> Result<(), std::fmt::Error>
    where
        W: std::fmt::Write,
    {
        for b in self.as_ref() {
            write!(&mut buf, "{b:02x}")?;
        }
        Ok(())
    }
}

impl From<aws_lc_rs::signature::Signature> for Signature {
    fn from(value: aws_lc_rs::signature::Signature) -> Self {
        let sig = value.as_ref();

        // Correctness: this is an infallible conversion because MAX_LEN is
        // always >= the size of a aws_lc_rs signature.
        //
        // This is asserted (and fuzzed) below.
        Self::try_from(sig).unwrap()
    }
}

impl TryFrom<&[u8]> for Signature {
    type Error = &'static str;

    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
        if value.len() > MAX_LEN {
            return Err("invalid signature: too long");
        }

        let mut data = [0; MAX_LEN];

        let sig = value;
        let dst = &mut data[..sig.len()];
        dst.clone_from_slice(sig);

        let len = u8::try_from(sig.len()).expect("signature is too large");

        Ok(Self { data, len })
    }
}

impl AsRef<[u8]> for Signature {
    fn as_ref(&self) -> &[u8] {
        &self.data[..self.len as usize]
    }
}

#[cfg(test)]
mod tests {
    use crate::{keys::PrivateKey, signer::Signer};

    use proptest::prelude::*;

    proptest! {
        #[test]
        fn prop_repr(
            payload in prop::collection::vec(any::<u8>(), 64),
        ) {
            let key = PrivateKey::new();
            let sig = key.sign(&payload);

            let mut hex = String::new();
            sig.as_hex_str(&mut hex).unwrap();

            // Debug
            let got = format!("{sig:?}");
            assert_eq!(got, format!("Signature({hex})"));

            // Display
            assert_eq!(sig.to_string(), hex);
        }
    }
}