curve25519_parser/
lib.rs

1use der_parser::der::*;
2use der_parser::error::BerError;
3
4use der_parser::oid::Oid;
5use der_parser::*;
6use nom::combinator::{complete, eof};
7use nom::IResult;
8
9use std::convert::{From, TryInto};
10
11use curve25519_dalek::edwards::CompressedEdwardsY;
12use curve25519_dalek::montgomery::MontgomeryPoint;
13use sha2::{Digest, Sha512};
14// Re-export x25519_dalek structures for convenience
15pub use x25519_dalek::{PublicKey, StaticSecret};
16
17use rand_core::{CryptoRng, RngCore};
18
19use std::fmt;
20
21const ED_25519_OID: Oid<'static> = oid!(1.3.101 .112);
22const X_25519_OID: Oid<'static> = oid!(1.3.101 .110);
23
24// ---- Error handling ----
25
26#[derive(Debug)]
27pub enum Curve25519ParserError {
28    /// BER Parsing error (wrong tag, not enough DER elements, etc.)
29    BerError(der_parser::error::BerError),
30    /// PEM Parsing error
31    PemError(pem::PemError),
32    /// Nom parsing error (wrong format, unexpected elements, etc.)
33    NomError(nom::Err<der_parser::error::BerError>),
34    UnknownOid,
35    InvalidData,
36    InvalidPEMTag,
37}
38impl From<der_parser::error::BerError> for Curve25519ParserError {
39    fn from(error: der_parser::error::BerError) -> Self {
40        Curve25519ParserError::BerError(error)
41    }
42}
43
44impl From<pem::PemError> for Curve25519ParserError {
45    fn from(error: pem::PemError) -> Self {
46        Curve25519ParserError::PemError(error)
47    }
48}
49
50impl From<nom::Err<der_parser::error::BerError>> for Curve25519ParserError {
51    fn from(error: nom::Err<der_parser::error::BerError>) -> Self {
52        Curve25519ParserError::NomError(error)
53    }
54}
55
56impl fmt::Display for Curve25519ParserError {
57    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
58        // For now, use the debug derived version
59        write!(f, "{self:?}")
60    }
61}
62
63// ---- Private key ----
64
65/// Expected structure:
66///
67/// ASN1:
68///    0:d=0  hl=2 l=  46 cons: SEQUENCE
69///    2:d=1  hl=2 l=   1 prim: INTEGER           :00
70///    5:d=1  hl=2 l=   5 cons: SEQUENCE
71///    7:d=2  hl=2 l=   3 prim: OBJECT            :ED25519 OR :X25519
72///   12:d=1  hl=2 l=  34 prim: OCTET STRING
73///
74/// Tree view:
75/// Seq(
76///     Int,
77///     Seq(
78///         OID(1.3.101.112), // ED25519 OR OID(1.3.101.110), // X25519
79///     ),
80///     OctetString(TAG_OCTETSTRING + LENGTH + DATA),
81/// )
82///
83/// From RFC8032, to obtain the corresponding `x25519_dalek::StaticSecret` from an ed25519 key:
84///   `clamping(Sha512(DATA)[0..32])`
85/// with `clamping` operation already done on `StaticSecret` creation
86
87#[derive(Debug, PartialEq)]
88struct Der25519PrivateHeader<'a> {
89    tag: DerObject<'a>,
90}
91
92#[derive(Debug, PartialEq)]
93struct Der25519PrivateStruct<'a> {
94    header: Der25519PrivateHeader<'a>,
95    data: DerObject<'a>,
96}
97
98fn parse_25519_private_header(i: &[u8]) -> IResult<&[u8], Der25519PrivateHeader, BerError> {
99    parse_der_container(|i: &[u8], hdr| {
100        if hdr.tag() != Tag::Sequence {
101            return Err(nom::Err::Error(BerError::InvalidTag));
102        }
103        let (i, tag) = parse_der_oid(i)?;
104        eof(i)?;
105        Ok((i, Der25519PrivateHeader { tag }))
106    })(i)
107}
108
109fn parse_25519_private(i: &[u8]) -> IResult<&[u8], Der25519PrivateStruct, BerError> {
110    parse_der_container(|i: &[u8], hdr| {
111        if hdr.tag() != Tag::Sequence {
112            return Err(nom::Err::Error(BerError::InvalidTag));
113        }
114        let (i, _unk) = parse_der_integer(i)?;
115        let (i, header) = complete(parse_25519_private_header)(i)?;
116        let (i, data) = parse_der_octetstring(i)?;
117        eof(i)?;
118        Ok((i, Der25519PrivateStruct { header, data }))
119    })(i)
120}
121
122const TAG_OCTETSTRING: u8 = 4;
123
124/// Parse a DER ED25519 or X25519 private key, and return the corresponding
125/// `x25519_dalek::StaticSecret`
126pub fn parse_openssl_25519_privkey_der(data: &[u8]) -> Result<StaticSecret, Curve25519ParserError> {
127    let (_remain, private) = parse_25519_private(data)?;
128    let data = private.data.content.as_slice()?;
129    // data[0] == TAG_OCTETSTRING(4)
130    // data[1] == LENGTH
131    if data.len() != 34 || data[0] != TAG_OCTETSTRING || data[1] != 32 {
132        return Err(Curve25519ParserError::InvalidData);
133    }
134    let mut key_data = [0u8; 32];
135
136    let read_oid = private.header.tag.as_oid()?;
137    if read_oid == &ED_25519_OID {
138        key_data.copy_from_slice(&Sha512::digest(&data[2..34])[0..32]);
139    } else if read_oid == &X_25519_OID {
140        key_data.copy_from_slice(&data[2..34]);
141    } else {
142        return Err(Curve25519ParserError::UnknownOid);
143    }
144    Ok(StaticSecret::from(key_data))
145}
146
147// ---- Public key ----
148
149/// Expected structure:
150///
151/// ASN1:
152///  0:d=0  hl=2 l=  42 cons: SEQUENCE
153///  2:d=1  hl=2 l=   5 cons: SEQUENCE
154///  4:d=2  hl=2 l=   3 prim: OBJECT            :ED25519
155///  9:d=1  hl=2 l=  33 prim: BIT STRING
156///
157/// Tree view:
158/// Seq(
159///     Seq(
160///         OID(1.3.101.112), // ED25519  OR  OID(1.3.101.110), // X25519
161///     ),
162///     BitString(DATA),
163/// )
164///
165/// From RFC8032 and OpenSSL format, to obtain the corresponding
166/// `x25519_dalek::PublicKey`, which internally use the Montgomery form
167/// from an Ed25519 key:
168///   to_montgomery(decompress_edwardspoint(DATA))
169
170#[derive(Debug, PartialEq)]
171struct DerEd25519PublicHeader<'a> {
172    tag: DerObject<'a>,
173}
174
175#[derive(Debug, PartialEq)]
176struct DerEd25519PublicStruct<'a> {
177    header: DerEd25519PublicHeader<'a>,
178    data: DerObject<'a>,
179}
180
181fn parse_25519_public_header(i: &[u8]) -> IResult<&[u8], DerEd25519PublicHeader, BerError> {
182    parse_der_container(|i: &[u8], hdr| {
183        if hdr.tag() != Tag::Sequence {
184            return Err(nom::Err::Error(BerError::InvalidTag));
185        }
186        let (i, tag) = parse_der_oid(i)?;
187        eof(i)?;
188        Ok((i, DerEd25519PublicHeader { tag }))
189    })(i)
190}
191
192fn parse_25519_public(i: &[u8]) -> IResult<&[u8], DerEd25519PublicStruct, BerError> {
193    parse_der_container(|i: &[u8], hdr| {
194        if hdr.tag() != Tag::Sequence {
195            return Err(nom::Err::Error(BerError::InvalidTag));
196        }
197        let (i, header) = complete(parse_25519_public_header)(i)?;
198        let (i, data) = parse_der_bitstring(i)?;
199        eof(i)?;
200        Ok((i, DerEd25519PublicStruct { header, data }))
201    })(i)
202}
203
204/// Parse a DER Ed25519 or X25519 public key, and return the corresponding
205/// `x25519_dalek::PublicKey`
206pub fn parse_openssl_25519_pubkey_der(data: &[u8]) -> Result<PublicKey, Curve25519ParserError> {
207    let (_remain, ed25519_public) = parse_25519_public(data)?;
208    let data = ed25519_public.data.content.as_slice()?;
209    let data: [u8; 32] = data
210        .try_into()
211        .map_err(|_| Curve25519ParserError::InvalidData)?;
212    let read_oid = ed25519_public.header.tag.as_oid()?;
213    if read_oid == &ED_25519_OID {
214        CompressedEdwardsY::from_slice(&data)
215            .ok()
216            .and_then(|c| c.decompress())
217            .map(|v| PublicKey::from(v.to_montgomery().to_bytes()))
218            .ok_or(Curve25519ParserError::InvalidData)
219    } else if read_oid == &X_25519_OID {
220        Ok(PublicKey::from(MontgomeryPoint(data).to_bytes()))
221    } else {
222        Err(Curve25519ParserError::UnknownOid)
223    }
224}
225
226// ---- PEM ----
227
228const PUBLIC_TAG: &[u8] = b"PUBLIC KEY";
229const PRIVATE_TAG: &[u8] = b"PRIVATE KEY";
230
231/// Parse an OpenSSL Ed25519 or X25519 public key, either in PEM or DER format
232pub fn parse_openssl_25519_pubkey(data: &[u8]) -> Result<PublicKey, Curve25519ParserError> {
233    if let Ok(pem_data) = pem::parse(data) {
234        // First, try as a PEM
235        if pem_data.tag().as_bytes() != PUBLIC_TAG {
236            return Err(Curve25519ParserError::InvalidPEMTag);
237        }
238        parse_openssl_25519_pubkey_der(pem_data.contents())
239    } else {
240        // Fallback to DER format
241        parse_openssl_25519_pubkey_der(data)
242    }
243}
244
245/// Parse an OpenSSL Ed25519 or X25519 private key, either in PEM or DER format
246pub fn parse_openssl_25519_privkey(data: &[u8]) -> Result<StaticSecret, Curve25519ParserError> {
247    if let Ok(pem_data) = pem::parse(data) {
248        // First, try as a PEM
249        if pem_data.tag().as_bytes() != PRIVATE_TAG {
250            return Err(Curve25519ParserError::InvalidPEMTag);
251        }
252        parse_openssl_25519_privkey_der(pem_data.contents())
253    } else {
254        // Fallback to DER format
255        parse_openssl_25519_privkey_der(data)
256    }
257}
258
259/// Parse several contiguous OpenSSL Ed25519 org X25519 public keys in PEM format
260pub fn parse_openssl_25519_pubkeys_pem_many(
261    data: &[u8],
262) -> Result<Vec<PublicKey>, Curve25519ParserError> {
263    let mut output = Vec::new();
264    for pem_data in pem::parse_many(data)? {
265        if pem_data.tag().as_bytes() != PUBLIC_TAG {
266            return Err(Curve25519ParserError::InvalidPEMTag);
267        }
268        output.push(parse_openssl_25519_pubkey_der(pem_data.contents())?);
269    }
270    Ok(output)
271}
272
273// ---- Strict Export ----
274
275// This is done with constant data instead of real DER building, as the format
276// is strict and key size are constant
277
278const PRIV_KEY_PREFIX: &[u8] = b"\x30\x2e\x02\x01\x00\x30\x05\x06\x03\x2b\x65\x6e\x04\x22\x04\x20";
279const PUB_KEY_PREFIX: &[u8] = b"\x30\x2a\x30\x05\x06\x03\x2b\x65\x6e\x03\x21\x00";
280const PRIV_KEY_TAG: &str = "PRIVATE KEY";
281const PUB_KEY_TAG: &str = "PUBLIC KEY";
282
283pub struct KeyPair {
284    pub public_der: [u8; PUB_KEY_PREFIX.len() + 32],
285    pub private_der: [u8; PRIV_KEY_PREFIX.len() + 32],
286}
287
288impl KeyPair {
289    pub fn public_as_pem(&self) -> String {
290        let out = pem::Pem::new(PUB_KEY_TAG, self.public_der.to_vec());
291        pem::encode(&out)
292    }
293
294    pub fn private_as_pem(&self) -> String {
295        let out = pem::Pem::new(PRIV_KEY_TAG, self.private_der.to_vec());
296        pem::encode(&out)
297    }
298}
299
300/// Generate a keypair, in DER format
301pub fn generate_keypair<T>(csprng: &mut T) -> Option<KeyPair>
302where
303    T: RngCore + CryptoRng,
304{
305    // Get the seed
306    let mut private = [0u8; 32];
307    csprng.fill_bytes(&mut private);
308
309    // Get the corresponding public key
310    let priv_key = StaticSecret::from(private);
311    let pubkey = PublicKey::from(&priv_key);
312
313    // Get the public data bytes
314
315    let public = pubkey.as_bytes();
316
317    let mut private_der = [0u8; PRIV_KEY_PREFIX.len() + 32];
318    private_der[..PRIV_KEY_PREFIX.len()].copy_from_slice(PRIV_KEY_PREFIX);
319    private_der[PRIV_KEY_PREFIX.len()..].copy_from_slice(&private);
320
321    let mut public_der = [0u8; PUB_KEY_PREFIX.len() + 32];
322    public_der[..PUB_KEY_PREFIX.len()].copy_from_slice(PUB_KEY_PREFIX);
323    public_der[PUB_KEY_PREFIX.len()..].copy_from_slice(&public[..]);
324
325    Some(KeyPair {
326        public_der,
327        private_der,
328    })
329}
330
331#[cfg(test)]
332mod tests {
333    use super::*;
334    use rand::rngs::OsRng;
335    use x25519_dalek::PublicKey;
336
337    // Samples, generated by:
338    // openssl genpkey -algorithm x25519 -outform DER -out test_x25519.der
339    static X_DER_PRIV: &[u8] = include_bytes!("../../samples/test_x25519.der");
340    // openssl pkey -outform DER -pubout -in test_x25519.der -inform DER -out test_x25519_pub.der
341    static X_DER_PUB: &[u8] = include_bytes!("../../samples/test_x25519_pub.der");
342
343    // openssl genpkey -algorithm ed25519 -outform DER -out test_ed25519.der
344    static ED_DER_PRIV: &[u8] = include_bytes!("../../samples/test_ed25519.der");
345    // openssl pkey -outform DER -pubout -in test_ed25519.der -inform DER -out test_ed25519_pub.der
346    static ED_DER_PUB: &[u8] = include_bytes!("../../samples/test_ed25519_pub.der");
347
348    // openssl pkey -in test_ed25519_pub.der -inform DER -pubin -out test_ed25519_pub.pem -outform PEM -pubout
349    static PEM_PUB: &[u8] = include_bytes!("../../samples/test_ed25519_pub.pem");
350    // openssl pkey -in test_ed25519.der -inform DER -out test_ed25519.pem -outform PEM
351    static PEM_PRIV: &[u8] = include_bytes!("../../samples/test_ed25519.pem");
352
353    // Many[0] is PEM_PUB
354    static PEM_PUB_MANY: &[u8] = include_bytes!("../../samples/test_25519_pub_many.pem");
355
356    #[test]
357    fn parse_and_check_ed_pubkeys_der() {
358        let priv_key = parse_openssl_25519_privkey_der(ED_DER_PRIV).unwrap();
359        let pub_key = parse_openssl_25519_pubkey_der(ED_DER_PUB).unwrap();
360        let computed_pub_key = PublicKey::from(&priv_key);
361        assert_eq!(pub_key.as_bytes().len(), 32);
362        assert_eq!(priv_key.to_bytes().len(), 32);
363        assert_eq!(computed_pub_key.as_bytes(), pub_key.as_bytes());
364    }
365
366    #[test]
367    fn parse_and_check_x_pubkeys_der() {
368        let priv_key = parse_openssl_25519_privkey_der(X_DER_PRIV).unwrap();
369        let pub_key = parse_openssl_25519_pubkey_der(X_DER_PUB).unwrap();
370        let computed_pub_key = PublicKey::from(&priv_key);
371        assert_eq!(pub_key.as_bytes().len(), 32);
372        assert_eq!(priv_key.to_bytes().len(), 32);
373        assert_eq!(computed_pub_key.as_bytes(), pub_key.as_bytes());
374    }
375
376    #[test]
377    fn parse_and_check_pubkeys_multi_format() {
378        let pub_key_pem = parse_openssl_25519_pubkey(PEM_PUB).unwrap();
379        let pub_key_der = parse_openssl_25519_pubkey(ED_DER_PUB).unwrap();
380        assert_eq!(pub_key_der.as_bytes().len(), 32);
381        assert_eq!(pub_key_der.as_bytes(), pub_key_pem.as_bytes());
382        let priv_key_pem = parse_openssl_25519_privkey(PEM_PRIV).unwrap();
383        let priv_key_der = parse_openssl_25519_privkey(ED_DER_PRIV).unwrap();
384        assert_eq!(priv_key_der.to_bytes().len(), 32);
385        assert_eq!(priv_key_der.to_bytes(), priv_key_pem.to_bytes());
386    }
387
388    #[test]
389    fn parse_many_pubkeys() {
390        let pub_keys_pem = parse_openssl_25519_pubkeys_pem_many(PEM_PUB).unwrap();
391        assert_eq!(pub_keys_pem.len(), 1);
392        let pub_key_der = parse_openssl_25519_pubkey(ED_DER_PUB).unwrap();
393        assert_eq!(pub_key_der.as_bytes().len(), 32);
394        assert_eq!(pub_key_der.as_bytes(), pub_keys_pem[0].as_bytes());
395
396        let pub_keys_pem = parse_openssl_25519_pubkeys_pem_many(PEM_PUB_MANY).unwrap();
397        assert_eq!(pub_keys_pem.len(), 3);
398        assert_eq!(pub_key_der.as_bytes(), pub_keys_pem[0].as_bytes());
399        assert_ne!(pub_key_der.as_bytes(), pub_keys_pem[1].as_bytes());
400
401        let pub_x_key_der = parse_openssl_25519_pubkey(X_DER_PUB).unwrap();
402        assert_eq!(pub_x_key_der.as_bytes(), pub_keys_pem[2].as_bytes());
403    }
404
405    #[test]
406    fn exports() {
407        let mut csprng = OsRng {};
408        let keypair = generate_keypair(&mut csprng).unwrap();
409
410        let priv_key = parse_openssl_25519_privkey_der(&keypair.private_der).unwrap();
411        let pub_key = parse_openssl_25519_pubkey_der(&keypair.public_der).unwrap();
412        let computed_pub_key = PublicKey::from(&priv_key);
413        assert_eq!(pub_key.as_bytes().len(), 32);
414        assert_eq!(priv_key.to_bytes().len(), 32);
415        assert_eq!(computed_pub_key.as_bytes(), pub_key.as_bytes());
416
417        let pub_pem_key = keypair.public_as_pem();
418        assert_eq!(
419            parse_openssl_25519_pubkey(pub_pem_key.as_bytes())
420                .unwrap()
421                .as_bytes(),
422            pub_key.as_bytes()
423        );
424        let priv_pem_key = keypair.private_as_pem();
425        assert_eq!(
426            &parse_openssl_25519_privkey(priv_pem_key.as_bytes())
427                .unwrap()
428                .to_bytes(),
429            &priv_key.to_bytes()
430        );
431    }
432}