hickory_proto/rr/dnssec/
key_format.rs

1#[cfg(feature = "openssl")]
2use openssl::ec::EcKey;
3#[cfg(feature = "openssl")]
4use openssl::rsa::Rsa;
5#[cfg(feature = "openssl")]
6use openssl::symm::Cipher;
7#[cfg(feature = "ring")]
8use ring::signature::{
9    EcdsaKeyPair, Ed25519KeyPair, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING,
10};
11
12use crate::error::*;
13use crate::rr::dnssec::{Algorithm, KeyPair, Private};
14
15/// The format of the binary key
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub enum KeyFormat {
18    /// A der encoded key
19    Der,
20    /// A pem encoded key, the default of OpenSSL
21    Pem,
22    /// Pkcs8, a pkcs8 formatted private key
23    Pkcs8,
24}
25
26impl KeyFormat {
27    /// Decode private key
28    #[allow(unused, clippy::match_single_binding)]
29    pub fn decode_key(
30        self,
31        bytes: &[u8],
32        password: Option<&str>,
33        algorithm: Algorithm,
34    ) -> DnsSecResult<KeyPair<Private>> {
35        //  empty string prevents openssl from triggering a read from stdin...
36        let password = password.unwrap_or("");
37        let password = password.as_bytes();
38
39        #[allow(deprecated)]
40        match algorithm {
41            Algorithm::Unknown(v) => Err(format!("unknown algorithm: {v}").into()),
42            #[cfg(feature = "openssl")]
43            e @ Algorithm::RSASHA1 | e @ Algorithm::RSASHA1NSEC3SHA1 => {
44                Err(format!("unsupported Algorithm (insecure): {e:?}").into())
45            }
46            #[cfg(feature = "openssl")]
47            Algorithm::RSASHA256 | Algorithm::RSASHA512 => {
48                let key = match self {
49                    Self::Der => Rsa::private_key_from_der(bytes)
50                        .map_err(|e| format!("error reading RSA as DER: {e}"))?,
51                    Self::Pem => {
52                        let key = Rsa::private_key_from_pem_passphrase(bytes, password);
53
54                        key.map_err(|e| {
55                            format!("could not decode RSA from PEM, bad password?: {e}")
56                        })?
57                    }
58                    e => {
59                        return Err(format!(
60                            "unsupported key format with RSA (DER or PEM only): \
61                             {e:?}"
62                        )
63                        .into())
64                    }
65                };
66
67                Ok(KeyPair::from_rsa(key)
68                    .map_err(|e| format!("could not translate RSA to KeyPair: {e}"))?)
69            }
70            Algorithm::ECDSAP256SHA256 | Algorithm::ECDSAP384SHA384 => match self {
71                #[cfg(feature = "openssl")]
72                Self::Der => {
73                    let key = EcKey::private_key_from_der(bytes)
74                        .map_err(|e| format!("error reading EC as DER: {e}"))?;
75
76                    Ok(KeyPair::from_ec_key(key)
77                        .map_err(|e| format!("could not translate RSA to KeyPair: {e}"))?)
78                }
79                #[cfg(feature = "openssl")]
80                Self::Pem => {
81                    let key = EcKey::private_key_from_pem_passphrase(bytes, password)
82                        .map_err(|e| format!("could not decode EC from PEM, bad password?: {e}"))?;
83
84                    Ok(KeyPair::from_ec_key(key)
85                        .map_err(|e| format!("could not translate RSA to KeyPair: {e}"))?)
86                }
87                #[cfg(feature = "ring")]
88                Self::Pkcs8 => {
89                    let ring_algorithm = if algorithm == Algorithm::ECDSAP256SHA256 {
90                        &ECDSA_P256_SHA256_FIXED_SIGNING
91                    } else {
92                        &ECDSA_P384_SHA384_FIXED_SIGNING
93                    };
94                    let rng = ring::rand::SystemRandom::new();
95                    let key = EcdsaKeyPair::from_pkcs8(ring_algorithm, bytes, &rng)?;
96
97                    Ok(KeyPair::from_ecdsa(key))
98                }
99                e => Err(format!("unsupported key format with EC: {e:?}").into()),
100            },
101            Algorithm::ED25519 => match self {
102                #[cfg(feature = "ring")]
103                Self::Pkcs8 => {
104                    let key = Ed25519KeyPair::from_pkcs8(bytes)?;
105
106                    Ok(KeyPair::from_ed25519(key))
107                }
108                e => Err(format!(
109                    "unsupported key format with ED25519 (only Pkcs8 supported): {e:?}"
110                )
111                .into()),
112            },
113            e => {
114                Err(format!("unsupported Algorithm, enable openssl or ring feature: {e:?}").into())
115            }
116        }
117    }
118
119    /// Generate a new key and encode to the specified format
120    pub fn generate_and_encode(
121        self,
122        algorithm: Algorithm,
123        password: Option<&str>,
124    ) -> DnsSecResult<Vec<u8>> {
125        // on encoding, if the password is empty string, ignore it (empty string is ok on decode)
126        #[allow(unused)]
127        let password = password
128            .iter()
129            .filter(|s| !s.is_empty())
130            .map(|s| s.as_bytes())
131            .next();
132
133        // generate the key
134        #[allow(unused, deprecated)]
135        let key_pair: KeyPair<Private> = match algorithm {
136            Algorithm::Unknown(v) => return Err(format!("unknown algorithm: {v}").into()),
137            #[cfg(feature = "openssl")]
138            e @ Algorithm::RSASHA1 | e @ Algorithm::RSASHA1NSEC3SHA1 => {
139                return Err(format!("unsupported Algorithm (insecure): {e:?}").into())
140            }
141            #[cfg(feature = "openssl")]
142            Algorithm::RSASHA256 | Algorithm::RSASHA512 => KeyPair::generate(algorithm)?,
143            Algorithm::ECDSAP256SHA256 | Algorithm::ECDSAP384SHA384 => match self {
144                #[cfg(feature = "openssl")]
145                Self::Der | Self::Pem => KeyPair::generate(algorithm)?,
146                #[cfg(feature = "ring")]
147                Self::Pkcs8 => return KeyPair::generate_pkcs8(algorithm),
148                e => return Err(format!("unsupported key format with EC: {e:?}").into()),
149            },
150            #[cfg(feature = "ring")]
151            Algorithm::ED25519 => return KeyPair::generate_pkcs8(algorithm),
152            e => {
153                return Err(
154                    format!("unsupported Algorithm, enable openssl or ring feature: {e:?}").into(),
155                )
156            }
157        };
158
159        // encode the key
160        #[allow(unreachable_code)]
161        match key_pair {
162            #[cfg(feature = "openssl")]
163            KeyPair::EC(ref pkey) | KeyPair::RSA(ref pkey) => {
164                match self {
165                    Self::Der => {
166                        // to avoid accidentally storing a key where there was an expectation that it was password protected
167                        if password.is_some() {
168                            return Err(format!("Can only password protect PEM: {self:?}").into());
169                        }
170                        pkey.private_key_to_der()
171                            .map_err(|e| format!("error writing key as DER: {e}").into())
172                    }
173                    Self::Pem => {
174                        let key = if let Some(password) = password {
175                            pkey.private_key_to_pem_pkcs8_passphrase(
176                                Cipher::aes_256_cbc(),
177                                password,
178                            )
179                        } else {
180                            pkey.private_key_to_pem_pkcs8()
181                        };
182
183                        key.map_err(|e| format!("error writing key as PEM: {e}").into())
184                    }
185                    e => Err(format!(
186                        "unsupported key format with RSA or EC (DER or PEM \
187                         only): {e:?}"
188                    )
189                    .into()),
190                }
191            }
192            #[cfg(feature = "ring")]
193            KeyPair::ECDSA(..) | KeyPair::ED25519(..) => panic!("should have returned early"),
194            #[cfg(not(feature = "openssl"))]
195            KeyPair::Phantom(..) => panic!("Phantom disallowed"),
196            #[cfg(not(any(feature = "openssl", feature = "ring")))]
197            _ => Err(format!(
198                "unsupported Algorithm, enable openssl feature (encode not supported with ring)"
199            )
200            .into()),
201        }
202    }
203
204    /// Encode private key
205    #[deprecated]
206    pub fn encode_key(
207        self,
208        key_pair: &KeyPair<Private>,
209        password: Option<&str>,
210    ) -> DnsSecResult<Vec<u8>> {
211        // on encoding, if the password is empty string, ignore it (empty string is ok on decode)
212        #[allow(unused)]
213        let password = password
214            .iter()
215            .filter(|s| !s.is_empty())
216            .map(|s| s.as_bytes())
217            .next();
218
219        match *key_pair {
220            #[cfg(feature = "openssl")]
221            KeyPair::EC(ref pkey) | KeyPair::RSA(ref pkey) => {
222                match self {
223                    Self::Der => {
224                        // to avoid accidentally storing a key where there was an expectation that it was password protected
225                        if password.is_some() {
226                            return Err(format!("Can only password protect PEM: {self:?}").into());
227                        }
228                        pkey.private_key_to_der()
229                            .map_err(|e| format!("error writing key as DER: {e}").into())
230                    }
231                    Self::Pem => {
232                        let key = if let Some(password) = password {
233                            pkey.private_key_to_pem_pkcs8_passphrase(
234                                Cipher::aes_256_cbc(),
235                                password,
236                            )
237                        } else {
238                            pkey.private_key_to_pem_pkcs8()
239                        };
240
241                        key.map_err(|e| format!("error writing key as PEM: {e}").into())
242                    }
243                    e => Err(format!(
244                        "unsupported key format with RSA or EC (DER or PEM \
245                         only): {e:?}"
246                    )
247                    .into()),
248                }
249            }
250            #[cfg(any(feature = "ring", not(feature = "openssl")))]
251            _ => Err(
252                "unsupported Algorithm, enable openssl feature (encode not supported with ring)"
253                    .into(),
254            ),
255        }
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    #![allow(clippy::dbg_macro, clippy::print_stdout)]
262
263    use super::*;
264
265    #[test]
266    #[cfg(feature = "openssl")]
267    fn test_rsa_encode_decode_der() {
268        let algorithm = Algorithm::RSASHA256;
269        encode_decode_with_format(KeyFormat::Der, algorithm, false, true);
270    }
271
272    #[test]
273    #[cfg(feature = "openssl")]
274    fn test_rsa_encode_decode_pem() {
275        let algorithm = Algorithm::RSASHA256;
276        encode_decode_with_format(KeyFormat::Pem, algorithm, true, true);
277    }
278
279    #[test]
280    #[cfg(feature = "openssl")]
281    fn test_ec_encode_decode_der() {
282        let algorithm = Algorithm::ECDSAP256SHA256;
283        encode_decode_with_format(KeyFormat::Der, algorithm, false, true);
284    }
285
286    #[test]
287    #[cfg(feature = "openssl")]
288    fn test_ec_encode_decode_pem() {
289        let algorithm = Algorithm::ECDSAP256SHA256;
290        encode_decode_with_format(KeyFormat::Pem, algorithm, true, true);
291    }
292
293    #[test]
294    #[cfg(feature = "ring")]
295    fn test_ec_encode_decode_pkcs8() {
296        let algorithm = Algorithm::ECDSAP256SHA256;
297        encode_decode_with_format(KeyFormat::Pkcs8, algorithm, true, true);
298    }
299
300    #[test]
301    #[cfg(feature = "ring")]
302    fn test_ed25519_encode_decode_pkcs8() {
303        let algorithm = Algorithm::ED25519;
304        encode_decode_with_format(KeyFormat::Pkcs8, algorithm, true, true);
305    }
306
307    #[cfg(test)]
308    fn encode_decode_with_format(
309        key_format: KeyFormat,
310        algorithm: Algorithm,
311        ok_pass: bool,
312        ok_empty_pass: bool,
313    ) {
314        let password = Some("test password");
315        let empty_password = Some("");
316        let no_password = None::<&str>;
317
318        encode_decode_with_password(key_format, password, password, algorithm, ok_pass, true);
319        encode_decode_with_password(
320            key_format,
321            empty_password,
322            empty_password,
323            algorithm,
324            ok_empty_pass,
325            true,
326        );
327        encode_decode_with_password(
328            key_format,
329            no_password,
330            no_password,
331            algorithm,
332            ok_empty_pass,
333            true,
334        );
335        encode_decode_with_password(
336            key_format,
337            no_password,
338            empty_password,
339            algorithm,
340            ok_empty_pass,
341            true,
342        );
343        encode_decode_with_password(
344            key_format,
345            empty_password,
346            no_password,
347            algorithm,
348            ok_empty_pass,
349            true,
350        );
351        // TODO: disabled for now... add back in if ring supports passwords on pkcs8
352        // encode_decode_with_password(key_format,
353        //                             password,
354        //                             no_password,
355        //                             algorithm,
356        //                             ok_pass,
357        //                             false);
358    }
359
360    #[cfg(test)]
361    fn encode_decode_with_password(
362        key_format: KeyFormat,
363        en_pass: Option<&str>,
364        de_pass: Option<&str>,
365        algorithm: Algorithm,
366        encode: bool,
367        decode: bool,
368    ) {
369        println!(
370            "test params: format: {key_format:?}, en_pass: {en_pass:?}, de_pass: {de_pass:?}, alg: {algorithm:?}, encode: {encode}, decode: {decode}"
371        );
372        let encoded_rslt = key_format.generate_and_encode(algorithm, en_pass);
373
374        if encode {
375            let encoded = encoded_rslt.expect("Encoding error");
376            let decoded = key_format.decode_key(&encoded, de_pass, algorithm);
377            assert_eq!(decoded.is_ok(), decode);
378        } else {
379            assert!(encoded_rslt.is_err());
380        }
381    }
382}