-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ssh-key: support additional SSH key algorithms (#136)
This introduces an additional "catch-all" key type to ssh-key to support additional SSH key algorithms, as described in #135. Adds an `AlgorithmName` type for additional algorithm names. The syntax for additional algorithm names is described in section 6 of RFC4251. Adds a new `Algorithm::Other` variant for representing additional algorithms. Breaking changes: `Algorithm::as_str`, `Algorithm::as_certificate_str` now return `&str` instead of `&static str`. Adds the `Keypair::Other` and `KeyData::Other` variants for storing the key material of keys that use a custom algorithm. Adds the `OpaqueKeypair` and `OpaqueKeyData` types for representing keys meant to be used with an algorithm unknown to this crate (e.g. custom algorithms). They are said to be opaque, because the meaning of their underlying byte representation is not specified.
- Loading branch information
Showing
17 changed files
with
597 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,8 @@ | ||
//! Algorithm support. | ||
|
||
#[cfg(feature = "alloc")] | ||
mod name; | ||
|
||
use crate::{Error, Result}; | ||
use core::{fmt, str}; | ||
use encoding::{Label, LabelError}; | ||
|
@@ -10,6 +13,9 @@ use { | |
sha2::{Digest, Sha256, Sha512}, | ||
}; | ||
|
||
#[cfg(feature = "alloc")] | ||
pub use name::AlgorithmName; | ||
|
||
/// bcrypt-pbkdf | ||
const BCRYPT: &str = "bcrypt"; | ||
|
||
|
@@ -80,7 +86,7 @@ const SK_SSH_ED25519: &str = "[email protected]"; | |
/// | ||
/// This type provides a registry of supported digital signature algorithms | ||
/// used for SSH keys. | ||
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] | ||
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] | ||
#[non_exhaustive] | ||
pub enum Algorithm { | ||
/// Digital Signature Algorithm | ||
|
@@ -113,6 +119,10 @@ pub enum Algorithm { | |
|
||
/// FIDO/U2F key with Ed25519 | ||
SkEd25519, | ||
|
||
/// Other | ||
#[cfg(feature = "alloc")] | ||
Other(AlgorithmName), | ||
} | ||
|
||
impl Algorithm { | ||
|
@@ -127,6 +137,8 @@ impl Algorithm { | |
/// - `ssh-rsa` | ||
/// - `[email protected]` (FIDO/U2F key) | ||
/// - `[email protected]` (FIDO/U2F key) | ||
/// | ||
/// Any other algorithms are mapped to the [`Algorithm::Other`] variant. | ||
pub fn new(id: &str) -> Result<Self> { | ||
Ok(id.parse()?) | ||
} | ||
|
@@ -147,6 +159,8 @@ impl Algorithm { | |
/// - `[email protected]` (FIDO/U2F key) | ||
/// - `[email protected]` (FIDO/U2F key) | ||
/// | ||
/// Any other algorithms are mapped to the [`Algorithm::Other`] variant. | ||
/// | ||
/// [PROTOCOL.certkeys]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD | ||
pub fn new_certificate(id: &str) -> Result<Self> { | ||
match id { | ||
|
@@ -164,12 +178,15 @@ impl Algorithm { | |
CERT_RSA => Ok(Algorithm::Rsa { hash: None }), | ||
CERT_SK_ECDSA_SHA2_P256 => Ok(Algorithm::SkEcdsaSha2NistP256), | ||
CERT_SK_SSH_ED25519 => Ok(Algorithm::SkEd25519), | ||
#[cfg(feature = "alloc")] | ||
_ => Ok(Algorithm::Other(AlgorithmName::from_certificate_str(id)?)), | ||
#[cfg(not(feature = "alloc"))] | ||
_ => Err(Error::AlgorithmUnknown), | ||
} | ||
} | ||
|
||
/// Get the string identifier which corresponds to this algorithm. | ||
pub fn as_str(self) -> &'static str { | ||
pub fn as_str(&self) -> &str { | ||
match self { | ||
Algorithm::Dsa => SSH_DSA, | ||
Algorithm::Ecdsa { curve } => match curve { | ||
|
@@ -185,6 +202,8 @@ impl Algorithm { | |
}, | ||
Algorithm::SkEcdsaSha2NistP256 => SK_ECDSA_SHA2_P256, | ||
Algorithm::SkEd25519 => SK_SSH_ED25519, | ||
#[cfg(feature = "alloc")] | ||
Algorithm::Other(algorithm) => algorithm.as_str(), | ||
} | ||
} | ||
|
||
|
@@ -195,7 +214,7 @@ impl Algorithm { | |
/// See [PROTOCOL.certkeys] for more information. | ||
/// | ||
/// [PROTOCOL.certkeys]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD | ||
pub fn as_certificate_str(self) -> &'static str { | ||
pub fn as_certificate_str(&self) -> &str { | ||
match self { | ||
Algorithm::Dsa => CERT_DSA, | ||
Algorithm::Ecdsa { curve } => match curve { | ||
|
@@ -207,6 +226,8 @@ impl Algorithm { | |
Algorithm::Rsa { .. } => CERT_RSA, | ||
Algorithm::SkEcdsaSha2NistP256 => CERT_SK_ECDSA_SHA2_P256, | ||
Algorithm::SkEd25519 => CERT_SK_SSH_ED25519, | ||
#[cfg(feature = "alloc")] | ||
Algorithm::Other(algorithm) => algorithm.certificate_str(), | ||
} | ||
} | ||
|
||
|
@@ -276,6 +297,9 @@ impl str::FromStr for Algorithm { | |
SSH_RSA => Ok(Algorithm::Rsa { hash: None }), | ||
SK_ECDSA_SHA2_P256 => Ok(Algorithm::SkEcdsaSha2NistP256), | ||
SK_SSH_ED25519 => Ok(Algorithm::SkEd25519), | ||
#[cfg(feature = "alloc")] | ||
_ => Ok(Algorithm::Other(AlgorithmName::from_str(id)?)), | ||
#[cfg(not(feature = "alloc"))] | ||
_ => Err(LabelError::new(id)), | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
use alloc::string::String; | ||
use core::str::{self, FromStr}; | ||
use encoding::LabelError; | ||
|
||
/// The suffix added to the `name` in a `name@domainname` algorithm string identifier. | ||
const CERT_STR_SUFFIX: &str = "-cert-v01"; | ||
|
||
/// According to [RFC4251 § 6], algorithm names are ASCII strings that are at most 64 | ||
/// characters long. | ||
/// | ||
/// [RFC4251 § 6]: https://www.rfc-editor.org/rfc/rfc4251.html#section-6 | ||
const MAX_ALGORITHM_NAME_LEN: usize = 64; | ||
|
||
/// The maximum length of the certificate string identifier is [`MAX_ALGORITHM_NAME_LEN`] + | ||
/// `"-cert-v01".len()` (the certificate identifier is obtained by inserting `"-cert-v01"` in the | ||
/// algorithm name). | ||
const MAX_CERT_STR_LEN: usize = MAX_ALGORITHM_NAME_LEN + CERT_STR_SUFFIX.len(); | ||
|
||
/// A string representing an additional algorithm name in the `name@domainname` format (see | ||
/// [RFC4251 § 6]). | ||
/// | ||
/// Additional algorithm names must be non-empty printable ASCII strings no longer than 64 | ||
/// characters. | ||
/// | ||
/// This also provides a `name-cert-v01@domainnname` string identifier for the corresponding | ||
/// OpenSSH certificate format, derived from the specified `name@domainname` string. | ||
/// | ||
/// NOTE: RFC4251 specifies additional validation criteria for algorithm names, but we do not | ||
/// implement all of them here. | ||
/// | ||
/// [RFC4251 § 6]: https://www.rfc-editor.org/rfc/rfc4251.html#section-6 | ||
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] | ||
pub struct AlgorithmName { | ||
/// The string identifier which corresponds to this algorithm. | ||
id: String, | ||
/// The string identifier which corresponds to the OpenSSH certificate format. | ||
/// | ||
/// This is derived from the algorithm name by inserting `"-cert-v01"` immediately after the | ||
/// name preceding the at-symbol (`@`). | ||
certificate_str: String, | ||
} | ||
|
||
impl AlgorithmName { | ||
/// Get the string identifier which corresponds to this algorithm name. | ||
pub fn as_str(&self) -> &str { | ||
&self.id | ||
} | ||
|
||
/// Get the string identifier which corresponds to the OpenSSH certificate format. | ||
pub fn certificate_str(&self) -> &str { | ||
&self.certificate_str | ||
} | ||
|
||
/// Create a new [`AlgorithmName`] from an OpenSSH certificate format string identifier. | ||
pub fn from_certificate_str(id: &str) -> Result<Self, LabelError> { | ||
validate_algorithm_id(id, MAX_CERT_STR_LEN)?; | ||
|
||
// Derive the algorithm name from the certificate format string identifier: | ||
let (name, domain) = split_algorithm_id(id)?; | ||
let name = name | ||
.strip_suffix(CERT_STR_SUFFIX) | ||
.ok_or_else(|| LabelError::new(id))?; | ||
|
||
let algorithm_name = format!("{name}@{domain}"); | ||
|
||
Ok(Self { | ||
id: algorithm_name, | ||
certificate_str: id.into(), | ||
}) | ||
} | ||
} | ||
|
||
impl FromStr for AlgorithmName { | ||
type Err = LabelError; | ||
|
||
fn from_str(id: &str) -> Result<Self, LabelError> { | ||
validate_algorithm_id(id, MAX_ALGORITHM_NAME_LEN)?; | ||
|
||
// Derive the certificate format string identifier from the algorithm name: | ||
let (name, domain) = split_algorithm_id(id)?; | ||
let certificate_str = format!("{name}{CERT_STR_SUFFIX}@{domain}"); | ||
|
||
Ok(Self { | ||
id: id.into(), | ||
certificate_str, | ||
}) | ||
} | ||
} | ||
|
||
/// Check if the length of `id` is at most `n`, and that `id` only consists of ASCII characters. | ||
fn validate_algorithm_id(id: &str, n: usize) -> Result<(), LabelError> { | ||
if id.len() > n || !id.is_ascii() { | ||
return Err(LabelError::new(id)); | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Split a `name@domainname` algorithm string identifier into `(name, domainname)`. | ||
fn split_algorithm_id(id: &str) -> Result<(&str, &str), LabelError> { | ||
let (name, domain) = id.split_once('@').ok_or_else(|| LabelError::new(id))?; | ||
|
||
// TODO: validate name and domain_name according to the criteria from RFC4251 | ||
if name.is_empty() || domain.is_empty() || domain.contains('@') { | ||
return Err(LabelError::new(id)); | ||
} | ||
|
||
Ok((name, domain)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.