Skip to main content

crypto/encoding/
ecdsa.rs

1//! DER encoding and decoding of ECDSA signatures.
2//!
3//! TLS and X.509 carry ECDSA signatures as the ASN.1 `ECDSA-Sig-Value`
4//! structure:
5//!
6//! ```text
7//! ECDSA-Sig-Value ::= SEQUENCE {
8//!     r  INTEGER,
9//!     s  INTEGER
10//! }
11//! ```
12//!
13//! The curve implementations in this crate operate on the fixed-width
14//! `r || s` form instead (`[u8; 64]` for P-256, `[u8; 96]` for P-384). These
15//! helpers convert between the two.
16
17use alloc::vec::Vec;
18
19use super::der::{DerError, Reader};
20
21const TAG_SEQUENCE: u8 = 0x30;
22const TAG_INTEGER: u8 = 0x02;
23
24/// Errors returned while converting ECDSA signatures to or from DER.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum EcdsaError {
27    /// The DER input was malformed.
28    Der(DerError),
29    /// The output buffer length was zero or not a multiple of two.
30    InvalidOutputLength,
31    /// An `INTEGER` encoded a negative value.
32    Negative,
33    /// An `INTEGER` did not fit in the fixed-width output.
34    IntegerTooLarge,
35}
36
37impl core::fmt::Display for EcdsaError {
38    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
39        match self {
40            EcdsaError::Der(err) => write!(f, "{err}"),
41            EcdsaError::InvalidOutputLength => write!(f, "output length must be a non-zero multiple of two"),
42            EcdsaError::Negative => write!(f, "ECDSA integer is negative"),
43            EcdsaError::IntegerTooLarge => write!(f, "ECDSA integer does not fit in the output"),
44        }
45    }
46}
47
48impl From<DerError> for EcdsaError {
49    fn from(err: DerError) -> Self {
50        EcdsaError::Der(err)
51    }
52}
53
54impl core::error::Error for EcdsaError {}
55
56fn push_length(out: &mut Vec<u8>, len: usize) {
57    if len < 0x80 {
58        out.push(len as u8);
59        return;
60    }
61    let bytes = len.to_be_bytes();
62    let first = bytes.iter().position(|b| *b != 0).unwrap_or(bytes.len() - 1);
63    let significant = &bytes[first..];
64    out.push(0x80 | significant.len() as u8);
65    out.extend_from_slice(significant);
66}
67
68fn encode_integer(value: &[u8], out: &mut Vec<u8>) {
69    // Strip leading zero bytes, keeping at least one byte for the value zero.
70    let mut start = 0;
71    while start + 1 < value.len() && value[start] == 0 {
72        start += 1;
73    }
74    let value = &value[start..];
75
76    // DER integers are signed, so a leading 0x00 is required when the most
77    // significant bit is set.
78    let pad = value[0] & 0x80 != 0;
79
80    out.push(TAG_INTEGER);
81    push_length(out, value.len() + pad as usize);
82    if pad {
83        out.push(0x00);
84    }
85    out.extend_from_slice(value);
86}
87
88/// Encodes the ECDSA values `r` and `s` (each big-endian, optionally with
89/// leading zero bytes) as an `ECDSA-Sig-Value` DER structure.
90pub fn encode_signature_der(r: &[u8], s: &[u8]) -> Vec<u8> {
91    let mut body = Vec::with_capacity(r.len() + s.len() + 8);
92    encode_integer(r, &mut body);
93    encode_integer(s, &mut body);
94
95    let mut out = Vec::with_capacity(body.len() + 4);
96    out.push(TAG_SEQUENCE);
97    push_length(&mut out, body.len());
98    out.extend_from_slice(&body);
99    out
100}
101
102fn write_fixed(value: &[u8], out: &mut [u8]) -> Result<(), EcdsaError> {
103    // DER integers are signed: a leading 0x00 is padding added to keep a
104    // positive value's high bit clear. Strip it before checking the sign.
105    let (negative, value) = match value {
106        [0x00, rest @ ..] => (false, rest),
107        [first, ..] => (first & 0x80 != 0, value),
108        [] => (false, value),
109    };
110    if negative {
111        return Err(EcdsaError::Negative);
112    }
113    if value.is_empty() {
114        // Encodes zero; leave the output as all zeros.
115        return Ok(());
116    }
117    if value.len() > out.len() {
118        return Err(EcdsaError::IntegerTooLarge);
119    }
120    let start = out.len() - value.len();
121    out[start..].copy_from_slice(value);
122    Ok(())
123}
124
125/// Decodes an `ECDSA-Sig-Value` DER structure into `raw_out`.
126///
127/// `raw_out` receives `r || s`, each zero-padded to half of its length. It
128/// must be non-empty and even.
129///
130/// Returns [`EcdsaError`] when the DER is malformed, when an integer is
131/// negative or too large for its half of the output, or when the lengths are
132/// inconsistent.
133pub fn decode_signature_der(der: &[u8], raw_out: &mut [u8]) -> Result<(), EcdsaError> {
134    if raw_out.is_empty() || !raw_out.len().is_multiple_of(2) {
135        return Err(EcdsaError::InvalidOutputLength);
136    }
137
138    let mut outer = Reader::new(der);
139    let mut seq = outer.read_sequence()?;
140    outer.finish()?;
141
142    let r = seq.read_integer()?;
143    let s = seq.read_integer()?;
144    seq.finish()?;
145
146    let (r_out, s_out) = raw_out.split_at_mut(raw_out.len() / 2);
147    r_out.fill(0);
148    s_out.fill(0);
149    write_fixed(r, r_out)?;
150    write_fixed(s, s_out)?;
151    Ok(())
152}
153
154/// Decodes an `ECDSA-Sig-Value` DER structure into a fixed-width P-256
155/// `r || s` signature.
156pub fn decode_p256_signature_der(der: &[u8]) -> Result<[u8; 64], EcdsaError> {
157    let mut raw = [0u8; 64];
158    decode_signature_der(der, &mut raw)?;
159    Ok(raw)
160}
161
162/// Decodes an `ECDSA-Sig-Value` DER structure into a fixed-width P-384
163/// `r || s` signature.
164pub fn decode_p384_signature_der(der: &[u8]) -> Result<[u8; 96], EcdsaError> {
165    let mut raw = [0u8; 96];
166    decode_signature_der(der, &mut raw)?;
167    Ok(raw)
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn encodes_small_values() {
176        let der = encode_signature_der(&[0x01], &[0x01]);
177        assert_eq!(der, [0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01]);
178    }
179
180    #[test]
181    fn encodes_with_sign_padding() {
182        let r = [0x80];
183        let s = [0x7f];
184        let der = encode_signature_der(&r, &s);
185        assert_eq!(der, [0x30, 0x07, 0x02, 0x02, 0x00, 0x80, 0x02, 0x01, 0x7f]);
186    }
187
188    #[test]
189    fn trims_leading_zeros() {
190        let r = [0x00, 0x00, 0x01];
191        let s = [0x00, 0x02];
192        let der = encode_signature_der(&r, &s);
193        assert_eq!(der, [0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02]);
194    }
195
196    #[test]
197    fn encodes_zero() {
198        let der = encode_signature_der(&[0x00], &[0x00]);
199        assert_eq!(der, [0x30, 0x06, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00]);
200    }
201
202    #[test]
203    fn round_trip_p256() {
204        let mut raw = [0u8; 64];
205        for (i, b) in raw.iter_mut().enumerate() {
206            *b = i as u8;
207        }
208        // Give r and s a high bit so DER adds padding.
209        raw[0] = 0xff;
210        raw[32] = 0xf0;
211
212        let der = encode_signature_der(&raw[..32], &raw[32..]);
213        let decoded = decode_p256_signature_der(&der).unwrap();
214        assert_eq!(decoded, raw);
215    }
216
217    #[test]
218    fn round_trip_p384() {
219        let mut raw = [0u8; 96];
220        for (i, b) in raw.iter_mut().enumerate() {
221            *b = (i * 7) as u8;
222        }
223        raw[0] = 0x80;
224        raw[48] = 0xff;
225
226        let der = encode_signature_der(&raw[..48], &raw[48..]);
227        let decoded = decode_p384_signature_der(&der).unwrap();
228        assert_eq!(decoded, raw);
229    }
230
231    #[test]
232    fn decode_rejects_trailing_data() {
233        let mut der = encode_signature_der(&[0x01], &[0x01]);
234        der.push(0xff);
235        assert!(matches!(
236            decode_signature_der(&der, &mut [0u8; 64]),
237            Err(EcdsaError::Der(DerError::TrailingData))
238        ));
239    }
240
241    #[test]
242    fn decode_rejects_negative_integer() {
243        // INTEGER with the high bit set and no sign padding byte.
244        let der = [0x30, 0x06, 0x02, 0x01, 0x80, 0x02, 0x01, 0x01];
245        assert_eq!(decode_signature_der(&der, &mut [0u8; 64]), Err(EcdsaError::Negative));
246    }
247
248    #[test]
249    fn decodes_positively_padded_integer() {
250        // r = 0x80 padded as 00 80, s = 1.
251        let der = [0x30, 0x07, 0x02, 0x02, 0x00, 0x80, 0x02, 0x01, 0x01];
252        let mut raw = [0u8; 64];
253        decode_signature_der(&der, &mut raw).unwrap();
254        assert_eq!(raw[31], 0x80);
255        assert_eq!(raw[63], 0x01);
256    }
257
258    #[test]
259    fn decode_rejects_oversized_integer() {
260        let value = [0x01u8; 33];
261        let der = encode_signature_der(&value, &[0x01]);
262        assert_eq!(decode_signature_der(&der, &mut [0u8; 64]), Err(EcdsaError::IntegerTooLarge));
263    }
264
265    #[test]
266    fn decode_rejects_bad_output_length() {
267        let der = encode_signature_der(&[0x01], &[0x01]);
268        assert_eq!(decode_signature_der(&der, &mut [0u8; 63]), Err(EcdsaError::InvalidOutputLength));
269    }
270}