Skip to main content

rc_crypto/
signature.rs

1// Copyright 2026-Present Datadog, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15const MAX_LEN: usize = 120;
16
17/// A signature generated by a [`PrivateKey`].
18///
19/// # Encoding
20///
21/// These signatures are, variable-length and utilise SHA256 as the internal
22/// cryptographic hash.
23///
24/// The returned signatures are encoded as ASN.1 wrapped DER bytes as described
25/// in [RFC 3279 § 2.2.3].
26///
27/// [`PrivateKey`]: crate::keys::PrivateKey
28/// [RFC 3279 § 2.2.3]: https://tools.ietf.org/html/rfc3279#section-2.2.3
29#[derive(Clone, PartialEq)]
30pub struct Signature {
31    data: [u8; MAX_LEN],
32    len: u8,
33}
34
35impl std::fmt::Display for Signature {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        self.as_hex_str(f)
38    }
39}
40
41impl std::fmt::Debug for Signature {
42    fn fmt(&self, mut f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        write!(f, "Signature(")?;
44        self.as_hex_str(&mut f)?;
45        write!(f, ")")
46    }
47}
48
49impl Signature {
50    /// Write this signature into `buf` formatted as a hex string.
51    pub fn as_hex_str<W>(&self, mut buf: W) -> Result<(), std::fmt::Error>
52    where
53        W: std::fmt::Write,
54    {
55        for b in self.as_ref() {
56            write!(&mut buf, "{b:02x}")?;
57        }
58        Ok(())
59    }
60}
61
62impl From<aws_lc_rs::signature::Signature> for Signature {
63    fn from(value: aws_lc_rs::signature::Signature) -> Self {
64        let sig = value.as_ref();
65
66        // Correctness: this is an infallible conversion because MAX_LEN is
67        // always >= the size of a aws_lc_rs signature.
68        //
69        // This is asserted (and fuzzed) below.
70        Self::try_from(sig).unwrap()
71    }
72}
73
74impl TryFrom<&[u8]> for Signature {
75    type Error = &'static str;
76
77    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
78        if value.len() > MAX_LEN {
79            return Err("invalid signature: too long");
80        }
81
82        let mut data = [0; MAX_LEN];
83
84        let sig = value;
85        let dst = &mut data[..sig.len()];
86        dst.clone_from_slice(sig);
87
88        let len = u8::try_from(sig.len()).expect("signature is too large");
89
90        Ok(Self { data, len })
91    }
92}
93
94impl AsRef<[u8]> for Signature {
95    fn as_ref(&self) -> &[u8] {
96        &self.data[..self.len as usize]
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use crate::{keys::PrivateKey, signer::Signer};
103
104    use proptest::prelude::*;
105
106    proptest! {
107        #[test]
108        fn prop_repr(
109            payload in prop::collection::vec(any::<u8>(), 64),
110        ) {
111            let key = PrivateKey::new();
112            let sig = key.sign(&payload);
113
114            let mut hex = String::new();
115            sig.as_hex_str(&mut hex).unwrap();
116
117            // Debug
118            let got = format!("{sig:?}");
119            assert_eq!(got, format!("Signature({hex})"));
120
121            // Display
122            assert_eq!(sig.to_string(), hex);
123        }
124    }
125}