Skip to main content

webauthn/
der.rs

1//! Minimal DER (Distinguished Encoding Rules) builder for RSA public keys.
2//!
3//! ring's RSA verification API expects the public key in DER-encoded
4//! RSAPublicKey format (`SEQUENCE { INTEGER n, INTEGER e }` per RFC 3447 §A.1.1),
5//! but COSE stores RSA keys as raw modulus (`n`) and exponent (`e`) byte arrays.
6//! This module bridges the two.
7//!
8//! Only the subset of DER needed to encode an RSA public key is implemented.
9//! No general-purpose ASN.1 library is pulled in — the structure is fixed and
10//! the encoding can be computed with simple byte concatenation.
11
12use crate::error::{Result, WebAuthnError};
13
14/// Encode a DER length field.
15///
16/// DER uses a variable-length encoding:
17/// - 1 byte for lengths 0–127
18/// - `0x81 nn` for lengths 128–255
19/// - `0x82 nn nn` for lengths 256–65535
20/// - `0x84 nn nn nn nn` for larger lengths (handles edge cases)
21pub fn der_length(len: usize) -> Vec<u8> {
22    if len < 0x80 {
23        vec![len as u8]
24    } else if len <= 0xFF {
25        vec![0x81, len as u8]
26    } else if len <= 0xFFFF {
27        vec![0x82, (len >> 8) as u8, (len & 0xFF) as u8]
28    } else {
29        // For RSA keys up to 4096-bit, this branch is unreachable in practice.
30        vec![
31            0x84,
32            (len >> 24) as u8,
33            (len >> 16) as u8,
34            (len >> 8) as u8,
35            len as u8,
36        ]
37    }
38}
39
40/// Wrap `contents` in a DER SEQUENCE (tag `0x30`).
41pub fn der_sequence(contents: &[u8]) -> Vec<u8> {
42    let mut out = vec![0x30];
43    out.extend_from_slice(&der_length(contents.len()));
44    out.extend_from_slice(contents);
45    out
46}
47
48/// Encode `value` as a DER INTEGER (tag `0x02`).
49///
50/// DER integers are signed. If the high bit of `value[0]` is set, a leading
51/// `0x00` byte is prepended so the value is not misread as negative.
52pub fn der_integer(value: &[u8]) -> Vec<u8> {
53    let needs_pad = !value.is_empty() && value[0] & 0x80 != 0;
54    let content_len = value.len() + if needs_pad { 1 } else { 0 };
55
56    let mut out = vec![0x02];
57    out.extend_from_slice(&der_length(content_len));
58    if needs_pad {
59        out.push(0x00);
60    }
61    out.extend_from_slice(value);
62    out
63}
64
65/// Wrap `contents` in a DER BIT STRING (tag `0x03`).
66///
67/// The leading `0x00` byte signals that no bits are unused in the final octet —
68/// required for all SubjectPublicKeyInfo public keys.
69pub fn der_bit_string(contents: &[u8]) -> Vec<u8> {
70    let content_len = contents.len() + 1; // +1 for the unused-bits byte
71    let mut out = vec![0x03];
72    out.extend_from_slice(&der_length(content_len));
73    out.push(0x00); // unused bits = 0
74    out.extend_from_slice(contents);
75    out
76}
77
78/// Encode `oid_bytes` as a DER OID (tag `0x06`).
79///
80/// `oid_bytes` must be the already-encoded OID value (not including tag/length).
81pub fn der_oid(oid_bytes: &[u8]) -> Vec<u8> {
82    let mut out = vec![0x06];
83    out.extend_from_slice(&der_length(oid_bytes.len()));
84    out.extend_from_slice(oid_bytes);
85    out
86}
87
88/// Return the two-byte DER NULL encoding (`0x05 0x00`).
89pub fn der_null() -> Vec<u8> {
90    vec![0x05, 0x00]
91}
92
93/// Build a DER-encoded `RSAPublicKey` from raw modulus and exponent bytes.
94///
95/// `ring`'s `UnparsedPublicKey` with `RSA_PKCS1_2048_8192_SHA256` parses the
96/// key as an RSAPublicKey (RFC 3447 §A.1.1), not SubjectPublicKeyInfo. COSE
97/// stores `n` and `e` as raw big-endian integers; this function wraps them in
98/// the ASN.1 structure ring requires.
99///
100/// # Structure produced
101/// ```text
102/// SEQUENCE {          ← RSAPublicKey (RFC 3447)
103///   INTEGER n         ← modulus
104///   INTEGER e         ← publicExponent
105/// }
106/// ```
107///
108/// # Arguments
109/// * `n` — RSA modulus as a big-endian byte array (256 bytes for 2048-bit key).
110/// * `e` — RSA public exponent as a big-endian byte array (typically `[0x01, 0x00, 0x01]`).
111///
112/// # Errors
113/// Returns [`WebAuthnError::InvalidPublicKey`] if `n` or `e` are empty.
114pub fn rsa_components_to_der(n: &[u8], e: &[u8]) -> Result<Vec<u8>> {
115    if n.is_empty() {
116        return Err(WebAuthnError::InvalidPublicKey(
117            "RSA modulus (n) must not be empty".to_string(),
118        ));
119    }
120    if e.is_empty() {
121        return Err(WebAuthnError::InvalidPublicKey(
122            "RSA exponent (e) must not be empty".to_string(),
123        ));
124    }
125
126    // RSAPublicKey SEQUENCE { INTEGER n, INTEGER e }
127    let mut contents = der_integer(n);
128    contents.extend_from_slice(&der_integer(e));
129    Ok(der_sequence(&contents))
130}
131
132// ─── Tests ────────────────────────────────────────────────────────────────────
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    // ── der_length ───────────────────────────────────────────────────────────
139
140    #[test]
141    fn der_length_short_form() {
142        assert_eq!(der_length(0), [0x00]);
143        assert_eq!(der_length(1), [0x01]);
144        assert_eq!(der_length(127), [0x7f]);
145    }
146
147    #[test]
148    fn der_length_one_byte_long_form() {
149        assert_eq!(der_length(128), [0x81, 0x80]);
150        assert_eq!(der_length(255), [0x81, 0xff]);
151    }
152
153    #[test]
154    fn der_length_two_byte_long_form() {
155        assert_eq!(der_length(256), [0x82, 0x01, 0x00]);
156        assert_eq!(der_length(290), [0x82, 0x01, 0x22]);
157        assert_eq!(der_length(65535), [0x82, 0xff, 0xff]);
158    }
159
160    // ── der_sequence ─────────────────────────────────────────────────────────
161
162    #[test]
163    fn der_sequence_empty() {
164        let encoded = der_sequence(&[]);
165        assert_eq!(encoded, [0x30, 0x00]);
166    }
167
168    #[test]
169    fn der_sequence_wraps_contents() {
170        let encoded = der_sequence(&[0xAA, 0xBB]);
171        assert_eq!(encoded, [0x30, 0x02, 0xAA, 0xBB]);
172    }
173
174    // ── der_integer ──────────────────────────────────────────────────────────
175
176    #[test]
177    fn der_integer_no_padding_needed() {
178        // 0x01 has high bit clear — no 0x00 prefix needed.
179        let encoded = der_integer(&[0x01, 0x00, 0x01]);
180        assert_eq!(encoded, [0x02, 0x03, 0x01, 0x00, 0x01]);
181    }
182
183    #[test]
184    fn der_integer_padding_needed_for_high_bit() {
185        // 0x80 has high bit set — DER requires a leading 0x00.
186        let encoded = der_integer(&[0x80]);
187        assert_eq!(encoded, [0x02, 0x02, 0x00, 0x80]);
188    }
189
190    #[test]
191    fn der_integer_ff_byte_gets_padded() {
192        let encoded = der_integer(&[0xFF, 0x00]);
193        assert_eq!(encoded, [0x02, 0x03, 0x00, 0xFF, 0x00]);
194    }
195
196    #[test]
197    fn der_integer_256_byte_value() {
198        // Simulate a 2048-bit RSA modulus starting with 0xB7 (high bit set).
199        let n = [0xB7u8; 256];
200        let encoded = der_integer(&n);
201        // tag + length(257) + 0x00 + 256 bytes
202        assert_eq!(encoded[0], 0x02);
203        assert_eq!(&encoded[1..4], [0x82, 0x01, 0x01]);
204        assert_eq!(encoded[4], 0x00); // padding byte
205        assert_eq!(encoded[5], 0xB7); // first byte of n
206        assert_eq!(encoded.len(), 1 + 3 + 1 + 256); // tag + len + pad + value
207    }
208
209    // ── der_bit_string ───────────────────────────────────────────────────────
210
211    #[test]
212    fn der_bit_string_prepends_unused_bits_byte() {
213        let encoded = der_bit_string(&[0xAB, 0xCD]);
214        // 0x03 (tag) | 0x03 (len=3) | 0x00 (unused bits) | 0xAB | 0xCD
215        assert_eq!(encoded, [0x03, 0x03, 0x00, 0xAB, 0xCD]);
216    }
217
218    // ── der_oid ──────────────────────────────────────────────────────────────
219
220    #[test]
221    fn der_oid_rsa_encryption() {
222        let encoded = der_oid(&[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01]);
223        assert_eq!(encoded[0], 0x06); // OID tag
224        assert_eq!(encoded[1], 9); // length = 9
225        assert_eq!(
226            &encoded[2..],
227            [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01]
228        );
229    }
230
231    // ── der_null ─────────────────────────────────────────────────────────────
232
233    #[test]
234    fn der_null_is_two_bytes() {
235        assert_eq!(der_null(), [0x05, 0x00]);
236    }
237
238    // ── rsa_components_to_der ────────────────────────────────────────────────
239
240    #[test]
241    fn rsa_components_to_der_rejects_empty_n() {
242        let err = rsa_components_to_der(&[], &[0x01, 0x00, 0x01]).expect_err("expected error");
243        assert!(matches!(err, WebAuthnError::InvalidPublicKey(ref m) if m.contains("n")));
244    }
245
246    #[test]
247    fn rsa_components_to_der_rejects_empty_e() {
248        let err = rsa_components_to_der(&[0x01u8; 256], &[]).expect_err("expected error");
249        assert!(matches!(err, WebAuthnError::InvalidPublicKey(ref m) if m.contains("e")));
250    }
251
252    #[test]
253    fn rsa_components_to_der_produces_correct_size_for_2048_bit_key() {
254        // n starting with 0x01 (high bit clear): no DER padding byte needed.
255        // INTEGER n = 1(tag)+3(len)+256(val) = 260 bytes
256        // INTEGER e = 1(tag)+1(len)+3(val) = 5 bytes
257        // SEQUENCE = 1(tag)+3(len)+265(contents) = 269 bytes
258        let n = vec![0x01u8; 256];
259        let e = [0x01u8, 0x00, 0x01];
260        let der = rsa_components_to_der(&n, &e).expect("test setup");
261        assert_eq!(der[0], 0x30, "must start with SEQUENCE tag");
262        assert_eq!(
263            der.len(),
264            269,
265            "RSAPublicKey for 2048-bit key (no DER padding) should be 269 bytes"
266        );
267    }
268
269    #[test]
270    fn rsa_components_to_der_produces_correct_size_with_high_bit_modulus() {
271        // n starting with 0xb7 (high bit set): DER padding byte prepended → n is 257 bytes.
272        // INTEGER n = 1(tag)+3(len)+257(val+pad) = 261 bytes
273        // INTEGER e = 1(tag)+1(len)+3(val) = 5 bytes
274        // SEQUENCE = 1(tag)+3(len)+266(contents) = 270 bytes
275        let n = vec![0xb7u8; 256];
276        let e = [0x01u8, 0x00, 0x01];
277        let der = rsa_components_to_der(&n, &e).expect("test setup");
278        assert_eq!(der[0], 0x30);
279        assert_eq!(
280            der.len(),
281            270,
282            "RSAPublicKey with 0x00-padded modulus should be 270 bytes"
283        );
284    }
285
286    #[test]
287    fn rsa_components_to_der_accepted_by_ring() {
288        // Use a real RSA 2048-bit public key (n, e from the test key hardcoded in
289        // crypto module tests). ring must accept the RSAPublicKey DER we produce.
290        let n: &[u8] = &[
291            0xb7, 0xe0, 0x0f, 0xc9, 0xdb, 0xfa, 0xce, 0x64, 0xa6, 0xe2, 0xb7, 0xfb, 0xa2, 0x1c,
292            0x09, 0x14, 0xfb, 0xd6, 0x26, 0xe5, 0x17, 0xcc, 0xf6, 0x6b, 0xf5, 0x8e, 0xbb, 0x69,
293            0x07, 0x50, 0xc0, 0xbb, 0x4c, 0xe7, 0x6e, 0xd8, 0xa4, 0x6a, 0x69, 0x29, 0xfc, 0xc9,
294            0x52, 0x0c, 0xdb, 0x04, 0xec, 0xa2, 0xef, 0x27, 0x7d, 0x8f, 0xfa, 0x9d, 0xaa, 0x10,
295            0x59, 0x54, 0x7b, 0x42, 0x78, 0xdb, 0xae, 0xd4, 0x24, 0x0a, 0xd4, 0x06, 0x69, 0xb0,
296            0xe2, 0xa5, 0x68, 0xca, 0x2d, 0x41, 0x34, 0xb0, 0x64, 0xaf, 0x61, 0x13, 0xc9, 0x32,
297            0xfc, 0x93, 0x56, 0x4f, 0x82, 0x7b, 0xea, 0xff, 0x20, 0xe5, 0x1c, 0x56, 0xb6, 0xe0,
298            0xf4, 0xaa, 0x6a, 0x20, 0xd2, 0x1c, 0x46, 0x71, 0xe6, 0x05, 0x9a, 0x96, 0x99, 0xad,
299            0x5a, 0x6f, 0x78, 0xfd, 0xa7, 0x06, 0xf8, 0xfd, 0x2d, 0xea, 0x91, 0xf2, 0x9e, 0xac,
300            0xc0, 0x43, 0x45, 0x2d, 0x79, 0xb0, 0xf2, 0x24, 0x5a, 0x8c, 0x91, 0xe6, 0xc6, 0xc2,
301            0xfe, 0x50, 0x8d, 0x64, 0x82, 0x06, 0x77, 0x6e, 0xef, 0x7d, 0x61, 0x6e, 0x80, 0xd1,
302            0x87, 0xfb, 0x25, 0x35, 0xc6, 0xe8, 0x3a, 0xec, 0x38, 0xce, 0x45, 0x70, 0xf8, 0x56,
303            0xc7, 0x6e, 0xb7, 0x20, 0xdb, 0x72, 0x51, 0x82, 0xd0, 0xd2, 0xd2, 0xbd, 0xc9, 0xe0,
304            0x3c, 0xef, 0xbb, 0x93, 0x70, 0xdd, 0xfb, 0xd4, 0xda, 0x6e, 0xf6, 0x73, 0xb3, 0x79,
305            0xf7, 0xe8, 0x49, 0x72, 0x22, 0x44, 0x92, 0xd8, 0xe4, 0x3e, 0x04, 0xbc, 0x83, 0xb2,
306            0x6c, 0x59, 0x4a, 0x79, 0x11, 0x1e, 0x33, 0xd6, 0x4b, 0xe6, 0x24, 0x7b, 0xdf, 0x93,
307            0x18, 0x1d, 0xb3, 0x27, 0x0b, 0x73, 0xbb, 0xff, 0xa8, 0xe2, 0x13, 0xa0, 0x8f, 0x39,
308            0x2c, 0x21, 0xc1, 0x5e, 0xf1, 0xa8, 0x82, 0x25, 0x28, 0x19, 0xae, 0xc9, 0x3f, 0x09,
309            0x2d, 0x8c, 0x81, 0xa5,
310        ];
311        let e: &[u8] = &[0x01, 0x00, 0x01];
312
313        let der = rsa_components_to_der(n, e).expect("test setup");
314        assert_eq!(der[0], 0x30);
315        assert_eq!(
316            der.len(),
317            270,
318            "RSAPublicKey for RSA 2048 (high-bit modulus) should be 270 bytes"
319        );
320
321        // The round-trip test (sign with ring, verify with our DER) lives in
322        // crypto::tests::verify_rs256_accepts_valid_signature.
323    }
324}