1use pkcs8::EncodePrivateKey;
2use rsa::RsaPrivateKey;
3use ssh_key::{EcdsaCurve, PrivateKey};
4
5use crate::error::SshKeyExportError;
6
7pub fn export_pkcs8_der_key(private_key: &str) -> Result<Vec<u8>, SshKeyExportError> {
12 let private_key =
14 PrivateKey::from_openssh(private_key).map_err(|_| SshKeyExportError::KeyConversion)?;
15
16 match private_key.key_data() {
17 ssh_key::private::KeypairData::Ed25519(keypair) => {
18 let sk: ed25519_dalek::SigningKey = keypair
19 .try_into()
20 .map_err(|_| SshKeyExportError::KeyConversion)?;
21
22 Ok(sk
23 .to_pkcs8_der()
24 .map_err(|_| SshKeyExportError::KeyConversion)?
25 .as_bytes()
26 .to_vec())
27 }
28 ssh_key::private::KeypairData::Rsa(keypair) => {
29 let rk: RsaPrivateKey = keypair
30 .try_into()
31 .map_err(|_| SshKeyExportError::KeyConversion)?;
32
33 Ok(rk
34 .to_pkcs8_der()
35 .map_err(|_| SshKeyExportError::KeyConversion)?
36 .as_bytes()
37 .to_vec())
38 }
39 ssh_key::private::KeypairData::Ecdsa(keypair) => export_ecdsa_pkcs8_der(keypair),
40 _ => Err(SshKeyExportError::KeyConversion),
41 }
42}
43
44fn export_ecdsa_pkcs8_der(
45 keypair: &ssh_key::private::EcdsaKeypair,
46) -> Result<Vec<u8>, SshKeyExportError> {
47 let curve = keypair.curve();
48 let private_key_bytes = keypair.private_key_bytes();
49
50 match curve {
51 EcdsaCurve::NistP256 => {
52 let sk = p256::SecretKey::from_slice(private_key_bytes)
53 .map_err(|_| SshKeyExportError::KeyConversion)?;
54 Ok(sk
55 .to_pkcs8_der()
56 .map_err(|_| SshKeyExportError::KeyConversion)?
57 .as_bytes()
58 .to_vec())
59 }
60 EcdsaCurve::NistP384 => {
61 let sk = p384::SecretKey::from_slice(private_key_bytes)
62 .map_err(|_| SshKeyExportError::KeyConversion)?;
63 Ok(sk
64 .to_pkcs8_der()
65 .map_err(|_| SshKeyExportError::KeyConversion)?
66 .as_bytes()
67 .to_vec())
68 }
69 EcdsaCurve::NistP521 => {
70 let sk = p521::SecretKey::from_slice(private_key_bytes)
71 .map_err(|_| SshKeyExportError::KeyConversion)?;
72 Ok(sk
73 .to_pkcs8_der()
74 .map_err(|_| SshKeyExportError::KeyConversion)?
75 .as_bytes()
76 .to_vec())
77 }
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use crate::import::{import_key, import_pkcs8_der_key};
85
86 #[test]
87 fn export_ed25519_openssh_unencrypted() {
88 let private_key = include_str!("../resources/import/ed25519_openssh_unencrypted");
89 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
90
91 let exported_key = export_pkcs8_der_key(&result.private_key).unwrap();
92 let expected_pkcs8_der: Vec<u8> = vec![
93 48, 81, 2, 1, 1, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32, 139, 118, 81, 75, 32, 150,
94 196, 136, 90, 63, 127, 68, 78, 117, 115, 13, 100, 3, 199, 24, 243, 97, 189, 182, 223,
95 181, 163, 236, 81, 145, 35, 104, 129, 33, 0, 50, 66, 141, 182, 77, 117, 205, 170, 241,
96 126, 47, 200, 212, 73, 35, 94, 187, 197, 42, 174, 192, 227, 189, 255, 105, 192, 140, 3,
97 11, 211, 11, 234,
98 ];
99 assert_eq!(exported_key, expected_pkcs8_der);
100
101 let reimported_key = import_pkcs8_der_key(&exported_key).unwrap();
103 assert_eq!(
104 reimported_key.public_key,
105 result.public_key.strip_suffix(" testkey").unwrap()
106 );
107 }
108
109 #[test]
110 fn export_rsa_openssh_unencrypted() {
111 let private_key = include_str!("../resources/import/rsa_openssh_unencrypted");
112 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
113
114 let exported_key = export_pkcs8_der_key(&result.private_key).unwrap();
115
116 let reimported_key = import_pkcs8_der_key(&exported_key).unwrap();
118 assert_eq!(
119 reimported_key.public_key,
120 result.public_key.strip_suffix(" testkey").unwrap()
121 );
122 }
123
124 #[test]
125 fn export_ecdsa_p256() {
126 let private_key = include_str!("../resources/generator/ecdsa_p256_key");
127 let exported_key = export_pkcs8_der_key(private_key).unwrap();
128 assert!(!exported_key.is_empty());
130 }
131
132 #[test]
133 fn export_ecdsa_p384() {
134 let private_key = include_str!("../resources/generator/ecdsa_p384_key");
135 let exported_key = export_pkcs8_der_key(private_key).unwrap();
136 assert!(!exported_key.is_empty());
137 }
138
139 #[test]
140 fn export_ecdsa_p521() {
141 let private_key = include_str!("../resources/generator/ecdsa_p521_key");
142 let exported_key = export_pkcs8_der_key(private_key).unwrap();
143 assert!(!exported_key.is_empty());
144 }
145
146 #[cfg(feature = "ecdsa-keys")]
147 #[test]
148 fn export_ecdsa_p256_roundtrip() {
149 let private_key = include_str!("../resources/generator/ecdsa_p256_key");
150 let view = import_key(private_key.to_string(), None).unwrap();
151
152 let exported_key = export_pkcs8_der_key(&view.private_key).unwrap();
153 let reimported = import_pkcs8_der_key(&exported_key).unwrap();
154 assert_eq!(reimported.public_key, view.public_key);
155 }
156
157 #[cfg(feature = "ecdsa-keys")]
158 #[test]
159 fn export_ecdsa_p384_roundtrip() {
160 let private_key = include_str!("../resources/generator/ecdsa_p384_key");
161 let view = import_key(private_key.to_string(), None).unwrap();
162
163 let exported_key = export_pkcs8_der_key(&view.private_key).unwrap();
164 let reimported = import_pkcs8_der_key(&exported_key).unwrap();
165 assert_eq!(reimported.public_key, view.public_key);
166 }
167
168 #[cfg(feature = "ecdsa-keys")]
169 #[test]
170 fn export_ecdsa_p521_roundtrip() {
171 let private_key = include_str!("../resources/generator/ecdsa_p521_key");
172 let view = import_key(private_key.to_string(), None).unwrap();
173
174 let exported_key = export_pkcs8_der_key(&view.private_key).unwrap();
175 let reimported = import_pkcs8_der_key(&exported_key).unwrap();
176 assert_eq!(reimported.public_key, view.public_key);
177 }
178}