-
Notifications
You must be signed in to change notification settings - Fork 4
/
mod.rs
253 lines (227 loc) · 9.77 KB
/
mod.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
//! Public key algorithms.
//!
//! The SSH protocol supports several public key algorithms, which are used to authenticate the
//! server and might also be used to authenticate the client.
//!
//! # Supported algorithms
//!
//! - "ssh-ed25519" ([`SSH_ED25519`], uses [`Ed25519Pubkey`] and [`Ed25519Privkey`])
//! - "ecdsa-sha2-nistp256" ([`ECDSA_SHA2_NISTP256`], uses [`EcdsaPubkey<p256::NistP256>`] and
//! [`EcdsaPrivkey<p256::NistP256>`])
//! - "ecdsa-sha2-nistp384" ([`ECDSA_SHA2_NISTP384`], uses [`EcdsaPubkey<p384::NistP384>`] and
//! [`EcdsaPrivkey<p384::NistP384>`])
//! - "ssh-rsa" ([`SSH_RSA_SHA1`], uses [`RsaPubkey`] and [`RsaPrivkey`])
//! - "rsa-sha2-256" ([`RSA_SHA2_256`], uses [`RsaPubkey`] and [`RsaPrivkey`])
//! - "rsa-sha2-512" ([`RSA_SHA2_512`], uses [`RsaPubkey`] and [`RsaPrivkey`])
use bytes::Bytes;
use derivative::Derivative;
use std::fmt;
use crate::codec::{PacketDecode, PacketEncode};
use crate::error::{Result, Error};
use crate::util::base64_encode;
pub use self::ecdsa::{ECDSA_SHA2_NISTP256, ECDSA_SHA2_NISTP384, EcdsaPubkey, EcdsaPrivkey};
pub use self::ed25519::{SSH_ED25519, Ed25519Pubkey, Ed25519Privkey};
pub use self::rsa::{SSH_RSA_SHA1, RSA_SHA2_256, RSA_SHA2_512, RsaPubkey, RsaPrivkey};
mod ecdsa;
mod ed25519;
mod rsa;
/// Algorithm for public key cryptography.
///
/// See the [module documentation][self] for details.
#[derive(Derivative)]
#[derivative(Debug)]
pub struct PubkeyAlgo {
/// Name of the algorithm.
pub name: &'static str,
#[derivative(Debug = "ignore")]
pub(crate) verify: fn(pubkey: &Pubkey, message: &[u8], signature: Bytes) -> Result<SignatureVerified>,
#[derivative(Debug = "ignore")]
pub(crate) sign: fn(privkey: &Privkey, message: &[u8]) -> Result<Bytes>,
}
/// Public key in one of supported formats.
///
/// This enum is marked as `#[non_exhaustive]`, so we might add new variants without breaking
/// backwards compatibility.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Pubkey {
/// Ed25519 public key.
Ed25519(Ed25519Pubkey),
/// RSA public key.
Rsa(RsaPubkey),
/// ECDSA public key on NIST P-256 curve.
EcdsaP256(EcdsaPubkey<p256::NistP256>),
/// ECDSA public key on NIST P-384 curve.
EcdsaP384(EcdsaPubkey<p384::NistP384>),
}
impl Pubkey {
/// Get all public key algorithms that work with this key.
///
/// Most key types work with just a single public key algorithm, but with RSA keys
/// ([`Pubkey::Rsa`]), there are multiple algorithms that differ in the hash function. This
/// method returns all supported algorithms for this key.
pub fn algos(&self) -> &'static [&'static PubkeyAlgo] {
static ED25519: &[&PubkeyAlgo] = &[&SSH_ED25519];
static RSA: &[&PubkeyAlgo] = &[&RSA_SHA2_256, &RSA_SHA2_512, &SSH_RSA_SHA1];
static ECDSA_P256: &[&PubkeyAlgo] = &[&ECDSA_SHA2_NISTP256];
static ECDSA_P384: &[&PubkeyAlgo] = &[&ECDSA_SHA2_NISTP384];
match self {
Pubkey::Ed25519(_) => ED25519,
Pubkey::Rsa(_) => RSA,
Pubkey::EcdsaP256(_) => ECDSA_P256,
Pubkey::EcdsaP384(_) => ECDSA_P384,
}
}
/// Get best public key algorithms that work with this key.
///
/// Most key types work with just a single public key algorithm, but with RSA keys
/// ([`Pubkey::Rsa`]), there are multiple algorithms that differ in the hash function. This
/// method returns only highly secure algorithms, but older servers may not support them.
#[deprecated(since = "0.2.1", note = "Disabling public key algorithms for authentication _on the client_ \
does not increase security, the older, less secure algorithms must be disabled on the server. \
Please use `Pubkey::algos()` instead.")]
pub fn algos_secure(&self) -> &'static [&'static PubkeyAlgo] {
static RSA: &[&PubkeyAlgo] = &[&RSA_SHA2_256, &RSA_SHA2_512];
match self {
Pubkey::Rsa(_) => RSA,
_ => self.algos(),
}
}
/// Get all public key algorithms that work with this key.
///
/// Most key types work with just a single public key algorithm, but with RSA keys
/// ([`Pubkey::Rsa`]), there are multiple algorithms that differ in the hash function. This
/// method returns all supported algorithms for maximum compatibility.
#[deprecated(since = "0.2.1", note = "Please use `Pubkey::algos()` instead.")]
pub fn algos_compatible_less_secure(&self) -> &'static [&'static PubkeyAlgo] {
self.algos()
}
/// Decode a public key from SSH wire encoding.
///
/// This is the encoding initially defined by RFC 4253. For keys other than RSA, the encoding
/// is defined in the RFC that introduces the key type.
pub fn decode(blob: Bytes) -> Result<Self> {
decode_pubkey(blob)
}
/// Encode a public key into SSH encoding.
///
/// This is the encoding initially defined by RFC 4253. For keys other than RSA, the encoding
/// is defined in the RFC that introduces the key type.
///
/// You can use this method to calculate a digest of the public key.
pub fn encode(&self) -> Bytes {
encode_pubkey(self)
}
/// Get the type of the key as a string.
///
/// Returns a string like `"ssh-rsa"` or `"ssh-ed25519"`, which is used in OpenSSH files like
/// `authorized_keys` or `known_hosts` as a human-readable description of the key type. It
/// corresponds to the string identifier of the key format that is present in the SSH encoding
/// of the key.
pub fn type_str(&self) -> String {
pubkey_type_str(self).into()
}
/// Compute a fingerprint of the public key.
///
/// The fingerprint is in the SHA-256 digest of the public key encoded with base64 (not padded
/// with `=` characters) and prefixed with `SHA256:` (e.g.
/// `"SHA256:eaBPG/rqx+IPa0Lc9KHypkG3UxjmUwerwq9CZ/xpPWM"`). If you need another format, use
/// [`encode()`][Self::encode()] to encode the key into bytes and apply a digest of your
/// choice.
pub fn fingerprint(&self) -> String {
use sha2::Digest;
let digest = sha2::Sha256::digest(self.encode());
format!("SHA256:{}", base64_encode(&digest))
}
}
impl fmt::Display for Pubkey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Pubkey::Ed25519(pubkey) => fmt::Display::fmt(pubkey, f),
Pubkey::Rsa(pubkey) => fmt::Display::fmt(pubkey, f),
Pubkey::EcdsaP256(pubkey) => fmt::Display::fmt(pubkey, f),
Pubkey::EcdsaP384(pubkey) => fmt::Display::fmt(pubkey, f),
}
}
}
#[derive(Debug)]
pub(crate) struct SignatureVerified(());
impl SignatureVerified {
fn assertion() -> Self { Self(()) }
}
/// Private key (keypair) in one of supported formats.
///
/// This enum is marked as `#[non_exhaustive]`, so we might add new variants without breaking
/// backwards compatibility.
#[derive(Clone, PartialEq, Eq)]
#[non_exhaustive]
#[cfg_attr(feature = "debug-less-secure", derive(Debug))]
pub enum Privkey {
/// Ed25519 private key.
Ed25519(Ed25519Privkey),
/// RSA private key.
Rsa(RsaPrivkey),
/// ECDSA private key on NIST P-256 curve.
EcdsaP256(EcdsaPrivkey<p256::NistP256>),
/// ECDSA private key on NIST P-384 curve.
EcdsaP384(EcdsaPrivkey<p384::NistP384>),
}
impl Privkey {
/// Return the public key associated with this private key.
pub fn pubkey(&self) -> Pubkey {
match self {
Privkey::Ed25519(privkey) => Pubkey::Ed25519(privkey.pubkey()),
Privkey::Rsa(privkey) => Pubkey::Rsa(privkey.pubkey()),
Privkey::EcdsaP256(privkey) => Pubkey::EcdsaP256(privkey.pubkey()),
Privkey::EcdsaP384(privkey) => Pubkey::EcdsaP384(privkey.pubkey()),
}
}
pub(crate) fn decode(blob: &mut PacketDecode) -> Result<Privkey> {
decode_privkey(blob)
}
}
fn decode_pubkey(blob: Bytes) -> Result<Pubkey> {
let mut blob = PacketDecode::new(blob);
let format = blob.get_string()?;
match format.as_str() {
"ssh-ed25519" => ed25519::decode_pubkey(&mut blob).map(Pubkey::Ed25519),
"ssh-rsa" => rsa::decode_pubkey(&mut blob).map(Pubkey::Rsa),
"ecdsa-sha2-nistp256" => ecdsa::decode_pubkey::<p256::NistP256>(&mut blob).map(Pubkey::EcdsaP256),
"ecdsa-sha2-nistp384" => ecdsa::decode_pubkey::<p384::NistP384>(&mut blob).map(Pubkey::EcdsaP384),
_ => {
log::debug!("unknown pubkey format {:?}", format);
Err(Error::Decode("unknown public key format"))
},
}
}
fn pubkey_type_str(pubkey: &Pubkey) -> &'static str {
match pubkey {
Pubkey::Ed25519(_) => "ssh-ed25519",
Pubkey::Rsa(_) => "ssh-rsa",
Pubkey::EcdsaP256(_) => "ecdsa-sha2-nistp256",
Pubkey::EcdsaP384(_) => "ecdsa-sha2-nistp384",
}
}
fn encode_pubkey(pubkey: &Pubkey) -> Bytes {
let mut blob = PacketEncode::new();
match pubkey {
Pubkey::Ed25519(pubkey) => ed25519::encode_pubkey(&mut blob, pubkey),
Pubkey::Rsa(pubkey) => rsa::encode_pubkey(&mut blob, pubkey),
Pubkey::EcdsaP256(pubkey) => ecdsa::encode_pubkey(&mut blob, pubkey),
Pubkey::EcdsaP384(pubkey) => ecdsa::encode_pubkey(&mut blob, pubkey),
}
blob.finish()
}
fn decode_privkey(blob: &mut PacketDecode) -> Result<Privkey> {
let format = blob.get_string()?;
match format.as_str() {
"ssh-ed25519" => ed25519::decode_privkey(blob).map(Privkey::Ed25519),
"ssh-rsa" => rsa::decode_privkey(blob).map(Privkey::Rsa),
"ecdsa-sha2-nistp256" => ecdsa::decode_privkey::<p256::NistP256>(blob).map(Privkey::EcdsaP256),
"ecdsa-sha2-nistp384" => ecdsa::decode_privkey::<p384::NistP384>(blob).map(Privkey::EcdsaP384),
_ => {
log::debug!("unknown privkey format {:?}", format);
Err(Error::Decode("unknown private key format"))
},
}
}