chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

515
vendor/ssh-key/src/algorithm.rs vendored Normal file
View File

@@ -0,0 +1,515 @@
//! Algorithm support.
#[cfg(feature = "alloc")]
mod name;
use crate::{Error, Result};
use core::{fmt, str};
use encoding::{Label, LabelError};
#[cfg(feature = "alloc")]
use {
alloc::{borrow::ToOwned, string::String, vec::Vec},
sha2::{Digest, Sha256, Sha512},
};
#[cfg(feature = "alloc")]
pub use name::AlgorithmName;
/// bcrypt-pbkdf
const BCRYPT: &str = "bcrypt";
/// OpenSSH certificate for DSA public key
const CERT_DSA: &str = "ssh-dss-cert-v01@openssh.com";
/// OpenSSH certificate for ECDSA (NIST P-256) public key
const CERT_ECDSA_SHA2_P256: &str = "ecdsa-sha2-nistp256-cert-v01@openssh.com";
/// OpenSSH certificate for ECDSA (NIST P-384) public key
const CERT_ECDSA_SHA2_P384: &str = "ecdsa-sha2-nistp384-cert-v01@openssh.com";
/// OpenSSH certificate for ECDSA (NIST P-521) public key
const CERT_ECDSA_SHA2_P521: &str = "ecdsa-sha2-nistp521-cert-v01@openssh.com";
/// OpenSSH certificate for Ed25519 public key
const CERT_ED25519: &str = "ssh-ed25519-cert-v01@openssh.com";
/// OpenSSH certificate with RSA public key
const CERT_RSA: &str = "ssh-rsa-cert-v01@openssh.com";
/// OpenSSH certificate for ECDSA (NIST P-256) U2F/FIDO security key
const CERT_SK_ECDSA_SHA2_P256: &str = "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com";
/// OpenSSH certificate for Ed25519 U2F/FIDO security key
const CERT_SK_SSH_ED25519: &str = "sk-ssh-ed25519-cert-v01@openssh.com";
/// ECDSA with SHA-256 + NIST P-256
const ECDSA_SHA2_P256: &str = "ecdsa-sha2-nistp256";
/// ECDSA with SHA-256 + NIST P-256
const ECDSA_SHA2_P384: &str = "ecdsa-sha2-nistp384";
/// ECDSA with SHA-256 + NIST P-256
const ECDSA_SHA2_P521: &str = "ecdsa-sha2-nistp521";
/// None
const NONE: &str = "none";
/// RSA with SHA-256 as described in RFC8332 § 3
const RSA_SHA2_256: &str = "rsa-sha2-256";
/// RSA with SHA-512 as described in RFC8332 § 3
const RSA_SHA2_512: &str = "rsa-sha2-512";
/// SHA-256 hash function
const SHA256: &str = "sha256";
/// SHA-512 hash function
const SHA512: &str = "sha512";
/// Digital Signature Algorithm
const SSH_DSA: &str = "ssh-dss";
/// Ed25519
const SSH_ED25519: &str = "ssh-ed25519";
/// RSA
const SSH_RSA: &str = "ssh-rsa";
/// U2F/FIDO security key with ECDSA/NIST P-256
const SK_ECDSA_SHA2_P256: &str = "sk-ecdsa-sha2-nistp256@openssh.com";
/// U2F/FIDO security key with Ed25519
const SK_SSH_ED25519: &str = "sk-ssh-ed25519@openssh.com";
/// SSH key algorithms.
///
/// This type provides a registry of supported digital signature algorithms
/// used for SSH keys.
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Algorithm {
/// Digital Signature Algorithm
Dsa,
/// Elliptic Curve Digital Signature Algorithm
Ecdsa {
/// Elliptic curve with which to instantiate ECDSA.
curve: EcdsaCurve,
},
/// Ed25519
#[default]
Ed25519,
/// RSA
Rsa {
/// Hash function to use with RSASSA-PKCS#1v15 signatures as specified
/// using [RFC8332] algorithm identifiers.
///
/// If `hash` is set to `None`, then `ssh-rsa` is used as the algorithm
/// name.
///
/// [RFC8332]: https://datatracker.ietf.org/doc/html/rfc8332
hash: Option<HashAlg>,
},
/// FIDO/U2F key with ECDSA/NIST-P256 + SHA-256
SkEcdsaSha2NistP256,
/// FIDO/U2F key with Ed25519
SkEd25519,
/// Other
#[cfg(feature = "alloc")]
Other(AlgorithmName),
}
impl Algorithm {
/// Decode algorithm from the given string identifier.
///
/// # Supported algorithms
/// - `ecdsa-sha2-nistp256`
/// - `ecdsa-sha2-nistp384`
/// - `ecdsa-sha2-nistp521`
/// - `ssh-dss`
/// - `ssh-ed25519`
/// - `ssh-rsa`
/// - `sk-ecdsa-sha2-nistp256@openssh.com` (FIDO/U2F key)
/// - `sk-ssh-ed25519@openssh.com` (FIDO/U2F key)
///
/// Any other algorithms are mapped to the [`Algorithm::Other`] variant.
pub fn new(id: &str) -> Result<Self> {
Ok(id.parse()?)
}
/// Decode algorithm from the given string identifier as used by
/// the OpenSSH certificate format.
///
/// OpenSSH certificate algorithms end in `*-cert-v01@openssh.com`.
/// See [PROTOCOL.certkeys] for more information.
///
/// # Supported algorithms
/// - `ssh-rsa-cert-v01@openssh.com`
/// - `ssh-dss-cert-v01@openssh.com`
/// - `ecdsa-sha2-nistp256-cert-v01@openssh.com`
/// - `ecdsa-sha2-nistp384-cert-v01@openssh.com`
/// - `ecdsa-sha2-nistp521-cert-v01@openssh.com`
/// - `ssh-ed25519-cert-v01@openssh.com`
/// - `sk-ecdsa-sha2-nistp256-cert-v01@openssh.com` (FIDO/U2F key)
/// - `sk-ssh-ed25519-cert-v01@openssh.com` (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 {
CERT_DSA => Ok(Algorithm::Dsa),
CERT_ECDSA_SHA2_P256 => Ok(Algorithm::Ecdsa {
curve: EcdsaCurve::NistP256,
}),
CERT_ECDSA_SHA2_P384 => Ok(Algorithm::Ecdsa {
curve: EcdsaCurve::NistP384,
}),
CERT_ECDSA_SHA2_P521 => Ok(Algorithm::Ecdsa {
curve: EcdsaCurve::NistP521,
}),
CERT_ED25519 => Ok(Algorithm::Ed25519),
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_type(id)?)),
#[cfg(not(feature = "alloc"))]
_ => Err(Error::AlgorithmUnknown),
}
}
/// Get the string identifier which corresponds to this algorithm.
pub fn as_str(&self) -> &str {
match self {
Algorithm::Dsa => SSH_DSA,
Algorithm::Ecdsa { curve } => match curve {
EcdsaCurve::NistP256 => ECDSA_SHA2_P256,
EcdsaCurve::NistP384 => ECDSA_SHA2_P384,
EcdsaCurve::NistP521 => ECDSA_SHA2_P521,
},
Algorithm::Ed25519 => SSH_ED25519,
Algorithm::Rsa { hash } => match hash {
None => SSH_RSA,
Some(HashAlg::Sha256) => RSA_SHA2_256,
Some(HashAlg::Sha512) => RSA_SHA2_512,
},
Algorithm::SkEcdsaSha2NistP256 => SK_ECDSA_SHA2_P256,
Algorithm::SkEd25519 => SK_SSH_ED25519,
#[cfg(feature = "alloc")]
Algorithm::Other(algorithm) => algorithm.as_str(),
}
}
/// Get the string identifier which corresponds to the OpenSSH certificate
/// format.
///
/// OpenSSH certificate algorithms end in `*-cert-v01@openssh.com`.
/// See [PROTOCOL.certkeys] for more information.
///
/// [PROTOCOL.certkeys]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
#[cfg(feature = "alloc")]
pub fn to_certificate_type(&self) -> String {
match self {
Algorithm::Dsa => CERT_DSA,
Algorithm::Ecdsa { curve } => match curve {
EcdsaCurve::NistP256 => CERT_ECDSA_SHA2_P256,
EcdsaCurve::NistP384 => CERT_ECDSA_SHA2_P384,
EcdsaCurve::NistP521 => CERT_ECDSA_SHA2_P521,
},
Algorithm::Ed25519 => CERT_ED25519,
Algorithm::Rsa { .. } => CERT_RSA,
Algorithm::SkEcdsaSha2NistP256 => CERT_SK_ECDSA_SHA2_P256,
Algorithm::SkEd25519 => CERT_SK_SSH_ED25519,
Algorithm::Other(algorithm) => return algorithm.certificate_type(),
}
.to_owned()
}
/// Is the algorithm DSA?
pub fn is_dsa(self) -> bool {
self == Algorithm::Dsa
}
/// Is the algorithm ECDSA?
pub fn is_ecdsa(self) -> bool {
matches!(self, Algorithm::Ecdsa { .. })
}
/// Is the algorithm Ed25519?
pub fn is_ed25519(self) -> bool {
self == Algorithm::Ed25519
}
/// Is the algorithm RSA?
pub fn is_rsa(self) -> bool {
matches!(self, Algorithm::Rsa { .. })
}
/// Return an error indicating this algorithm is unsupported.
#[allow(dead_code)]
pub(crate) fn unsupported_error(self) -> Error {
Error::AlgorithmUnsupported { algorithm: self }
}
}
impl AsRef<str> for Algorithm {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Label for Algorithm {}
impl fmt::Display for Algorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl str::FromStr for Algorithm {
type Err = LabelError;
fn from_str(id: &str) -> core::result::Result<Self, LabelError> {
match id {
SSH_DSA => Ok(Algorithm::Dsa),
ECDSA_SHA2_P256 => Ok(Algorithm::Ecdsa {
curve: EcdsaCurve::NistP256,
}),
ECDSA_SHA2_P384 => Ok(Algorithm::Ecdsa {
curve: EcdsaCurve::NistP384,
}),
ECDSA_SHA2_P521 => Ok(Algorithm::Ecdsa {
curve: EcdsaCurve::NistP521,
}),
RSA_SHA2_256 => Ok(Algorithm::Rsa {
hash: Some(HashAlg::Sha256),
}),
RSA_SHA2_512 => Ok(Algorithm::Rsa {
hash: Some(HashAlg::Sha512),
}),
SSH_ED25519 => Ok(Algorithm::Ed25519),
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)),
}
}
}
/// Elliptic curves supported for use with ECDSA.
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum EcdsaCurve {
/// NIST P-256 (a.k.a. prime256v1, secp256r1)
NistP256,
/// NIST P-384 (a.k.a. secp384r1)
NistP384,
/// NIST P-521 (a.k.a. secp521r1)
NistP521,
}
impl EcdsaCurve {
/// Decode elliptic curve from the given string identifier.
///
/// # Supported curves
///
/// - `nistp256`
/// - `nistp384`
/// - `nistp521`
pub fn new(id: &str) -> Result<Self> {
Ok(id.parse()?)
}
/// Get the string identifier which corresponds to this ECDSA elliptic curve.
pub fn as_str(self) -> &'static str {
match self {
EcdsaCurve::NistP256 => "nistp256",
EcdsaCurve::NistP384 => "nistp384",
EcdsaCurve::NistP521 => "nistp521",
}
}
/// Get the number of bytes needed to encode a field element for this curve.
#[cfg(feature = "alloc")]
pub(crate) const fn field_size(self) -> usize {
match self {
EcdsaCurve::NistP256 => 32,
EcdsaCurve::NistP384 => 48,
EcdsaCurve::NistP521 => 66,
}
}
}
impl AsRef<str> for EcdsaCurve {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Label for EcdsaCurve {}
impl fmt::Display for EcdsaCurve {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl str::FromStr for EcdsaCurve {
type Err = LabelError;
fn from_str(id: &str) -> core::result::Result<Self, LabelError> {
match id {
"nistp256" => Ok(EcdsaCurve::NistP256),
"nistp384" => Ok(EcdsaCurve::NistP384),
"nistp521" => Ok(EcdsaCurve::NistP521),
_ => Err(LabelError::new(id)),
}
}
}
/// Hashing algorithms a.k.a. digest functions.
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum HashAlg {
/// SHA-256
#[default]
Sha256,
/// SHA-512
Sha512,
}
impl HashAlg {
/// Decode elliptic curve from the given string identifier.
///
/// # Supported hash algorithms
///
/// - `sha256`
/// - `sha512`
pub fn new(id: &str) -> Result<Self> {
Ok(id.parse()?)
}
/// Get the string identifier for this hash algorithm.
pub fn as_str(self) -> &'static str {
match self {
HashAlg::Sha256 => SHA256,
HashAlg::Sha512 => SHA512,
}
}
/// Get the size of a digest produced by this hash function.
pub const fn digest_size(self) -> usize {
match self {
HashAlg::Sha256 => 32,
HashAlg::Sha512 => 64,
}
}
/// Compute a digest of the given message using this hash function.
#[cfg(feature = "alloc")]
pub fn digest(self, msg: &[u8]) -> Vec<u8> {
match self {
HashAlg::Sha256 => Sha256::digest(msg).to_vec(),
HashAlg::Sha512 => Sha512::digest(msg).to_vec(),
}
}
}
impl Label for HashAlg {}
impl AsRef<str> for HashAlg {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for HashAlg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl str::FromStr for HashAlg {
type Err = LabelError;
fn from_str(id: &str) -> core::result::Result<Self, LabelError> {
match id {
SHA256 => Ok(HashAlg::Sha256),
SHA512 => Ok(HashAlg::Sha512),
_ => Err(LabelError::new(id)),
}
}
}
/// Key Derivation Function (KDF) algorithms.
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum KdfAlg {
/// None.
None,
/// bcrypt-pbkdf.
#[default]
Bcrypt,
}
impl KdfAlg {
/// Decode KDF algorithm from the given `kdfname`.
///
/// # Supported KDF names
/// - `none`
pub fn new(kdfname: &str) -> Result<Self> {
Ok(kdfname.parse()?)
}
/// Get the string identifier which corresponds to this algorithm.
pub fn as_str(self) -> &'static str {
match self {
Self::None => NONE,
Self::Bcrypt => BCRYPT,
}
}
/// Is the KDF algorithm "none"?
pub fn is_none(self) -> bool {
self == Self::None
}
}
impl Label for KdfAlg {}
impl AsRef<str> for KdfAlg {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for KdfAlg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl str::FromStr for KdfAlg {
type Err = LabelError;
fn from_str(kdfname: &str) -> core::result::Result<Self, LabelError> {
match kdfname {
NONE => Ok(Self::None),
BCRYPT => Ok(Self::Bcrypt),
_ => Err(LabelError::new(kdfname)),
}
}
}

101
vendor/ssh-key/src/algorithm/name.rs vendored Normal file
View File

@@ -0,0 +1,101 @@
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,
}
impl AlgorithmName {
/// Create a new algorithm identifier.
pub fn new(id: impl Into<String>) -> Result<Self, LabelError> {
let id = id.into();
validate_algorithm_id(&id, MAX_ALGORITHM_NAME_LEN)?;
split_algorithm_id(&id)?;
Ok(Self { id })
}
/// 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_type(&self) -> String {
let (name, domain) = split_algorithm_id(&self.id).expect("format checked in constructor");
format!("{name}{CERT_STR_SUFFIX}@{domain}")
}
/// Create a new [`AlgorithmName`] from an OpenSSH certificate format string identifier.
pub fn from_certificate_type(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 })
}
}
impl FromStr for AlgorithmName {
type Err = LabelError;
fn from_str(id: &str) -> Result<Self, LabelError> {
Self::new(id)
}
}
/// 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))
}

385
vendor/ssh-key/src/authorized_keys.rs vendored Normal file
View File

@@ -0,0 +1,385 @@
//! Parser for `AuthorizedKeysFile`-formatted data.
use crate::{Error, PublicKey, Result};
use core::str;
#[cfg(feature = "alloc")]
use {
alloc::string::{String, ToString},
core::fmt,
};
#[cfg(feature = "std")]
use std::{fs, path::Path, vec::Vec};
/// Character that begins a comment
const COMMENT_DELIMITER: char = '#';
/// Parser for `AuthorizedKeysFile`-formatted data, typically found in
/// `~/.ssh/authorized_keys`.
///
/// For a full description of the format, see:
/// <https://man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE_FORMAT>
///
/// Each line of the file consists of a single public key. Blank lines are ignored.
///
/// Public keys consist of the following space-separated fields:
///
/// ```text
/// options, keytype, base64-encoded key, comment
/// ```
///
/// - The options field is optional.
/// - The keytype is `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`,
/// `ssh-ed25519`, `ssh-dss` or `ssh-rsa`
/// - The comment field is not used for anything (but may be convenient for the user to identify
/// the key).
pub struct AuthorizedKeys<'a> {
/// Lines of the file being iterated over
lines: str::Lines<'a>,
}
impl<'a> AuthorizedKeys<'a> {
/// Create a new parser for the given input buffer.
pub fn new(input: &'a str) -> Self {
Self {
lines: input.lines(),
}
}
/// Read an [`AuthorizedKeys`] file from the filesystem, returning an
/// [`Entry`] vector on success.
#[cfg(feature = "std")]
pub fn read_file(path: impl AsRef<Path>) -> Result<Vec<Entry>> {
// TODO(tarcieri): permissions checks
let input = fs::read_to_string(path)?;
AuthorizedKeys::new(&input).collect()
}
/// Get the next line, trimming any comments and trailing whitespace.
///
/// Ignores empty lines.
fn next_line_trimmed(&mut self) -> Option<&'a str> {
loop {
let mut line = self.lines.next()?;
// Strip comment if present
if let Some((l, _)) = line.split_once(COMMENT_DELIMITER) {
line = l;
}
// Trim trailing whitespace
line = line.trim_end();
if !line.is_empty() {
return Some(line);
}
}
}
}
impl Iterator for AuthorizedKeys<'_> {
type Item = Result<Entry>;
fn next(&mut self) -> Option<Result<Entry>> {
self.next_line_trimmed().map(|line| line.parse())
}
}
/// Individual entry in an `authorized_keys` file containing a single public key.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Entry {
/// Configuration options field, if present.
#[cfg(feature = "alloc")]
config_opts: ConfigOpts,
/// Public key
public_key: PublicKey,
}
impl Entry {
/// Get configuration options for this entry.
#[cfg(feature = "alloc")]
pub fn config_opts(&self) -> &ConfigOpts {
&self.config_opts
}
/// Get public key for this entry.
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
}
#[cfg(feature = "alloc")]
impl From<Entry> for ConfigOpts {
fn from(entry: Entry) -> ConfigOpts {
entry.config_opts
}
}
impl From<Entry> for PublicKey {
fn from(entry: Entry) -> PublicKey {
entry.public_key
}
}
impl From<PublicKey> for Entry {
fn from(public_key: PublicKey) -> Entry {
Entry {
#[cfg(feature = "alloc")]
config_opts: ConfigOpts::default(),
public_key,
}
}
}
impl str::FromStr for Entry {
type Err = Error;
fn from_str(line: &str) -> Result<Self> {
match line.matches(' ').count() {
1..=2 => Ok(Self {
#[cfg(feature = "alloc")]
config_opts: Default::default(),
public_key: line.parse()?,
}),
3.. => {
// Having >= 3 spaces is ambiguous: it's either a key preceded
// by options, or a key with spaces in its comment. We'll try
// parsing as a single key first, then fall back to parsing as
// option + key.
match line.parse() {
Ok(public_key) => Ok(Self {
#[cfg(feature = "alloc")]
config_opts: Default::default(),
public_key,
}),
Err(_) => line
.split_once(' ')
.map(|(config_opts_str, public_key_str)| {
ConfigOptsIter(config_opts_str).validate()?;
Ok(Self {
#[cfg(feature = "alloc")]
config_opts: ConfigOpts(config_opts_str.to_string()),
public_key: public_key_str.parse()?,
})
})
.ok_or(Error::FormatEncoding)?,
}
}
_ => Err(Error::FormatEncoding),
}
}
}
#[cfg(feature = "alloc")]
impl ToString for Entry {
fn to_string(&self) -> String {
let mut s = String::new();
if !self.config_opts.is_empty() {
s.push_str(self.config_opts.as_str());
s.push(' ');
}
s.push_str(&self.public_key.to_string());
s
}
}
/// Configuration options associated with a particular public key.
///
/// These options are a comma-separated list preceding each public key
/// in the `authorized_keys` file.
///
/// The [`ConfigOpts::iter`] method can be used to iterate over each
/// comma-separated value.
#[cfg(feature = "alloc")]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ConfigOpts(String);
#[cfg(feature = "alloc")]
impl ConfigOpts {
/// Parse an options string.
pub fn new(string: impl Into<String>) -> Result<Self> {
let ret = Self(string.into());
ret.iter().validate()?;
Ok(ret)
}
/// Borrow the configuration options as a `str`.
pub fn as_str(&self) -> &str {
self.0.as_str()
}
/// Are there no configuration options?
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Iterate over the comma-delimited configuration options.
pub fn iter(&self) -> ConfigOptsIter<'_> {
ConfigOptsIter(self.as_str())
}
}
#[cfg(feature = "alloc")]
impl AsRef<str> for ConfigOpts {
fn as_ref(&self) -> &str {
self.as_str()
}
}
#[cfg(feature = "alloc")]
impl str::FromStr for ConfigOpts {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::new(s)
}
}
#[cfg(feature = "alloc")]
impl fmt::Display for ConfigOpts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
/// Iterator over configuration options.
#[derive(Clone, Debug)]
pub struct ConfigOptsIter<'a>(&'a str);
impl<'a> ConfigOptsIter<'a> {
/// Create new configuration options iterator.
///
/// Validates that the options are well-formed.
pub fn new(s: &'a str) -> Result<Self> {
let ret = Self(s);
ret.clone().validate()?;
Ok(ret)
}
/// Validate that config options are well-formed.
fn validate(&mut self) -> Result<()> {
while self.try_next()?.is_some() {}
Ok(())
}
/// Attempt to parse the next comma-delimited option string.
fn try_next(&mut self) -> Result<Option<&'a str>> {
if self.0.is_empty() {
return Ok(None);
}
let mut quoted = false;
let mut index = 0;
while let Some(byte) = self.0.as_bytes().get(index).cloned() {
match byte {
b',' => {
// Commas inside quoted text are ignored
if !quoted {
let (next, rest) = self.0.split_at(index);
self.0 = &rest[1..]; // Strip comma
return Ok(Some(next));
}
}
// TODO(tarcieri): stricter handling of quotes
b'"' => {
// Toggle quoted mode on-off
quoted = !quoted;
}
// Valid characters
b'A'..=b'Z'
| b'a'..=b'z'
| b'0'..=b'9'
| b'!'..=b'/'
| b':'..=b'@'
| b'['..=b'_'
| b'{'
| b'}'
| b'|'
| b'~' => (),
_ => return Err(encoding::Error::CharacterEncoding.into()),
}
index = index.checked_add(1).ok_or(encoding::Error::Length)?;
}
let remaining = self.0;
self.0 = "";
Ok(Some(remaining))
}
}
impl<'a> Iterator for ConfigOptsIter<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<&'a str> {
// Ensured valid by constructor
self.try_next().expect("malformed options string")
}
}
#[cfg(all(test, feature = "alloc"))]
mod tests {
use super::ConfigOptsIter;
#[test]
fn options_empty() {
assert_eq!(ConfigOptsIter("").try_next(), Ok(None));
}
#[test]
fn options_no_comma() {
let mut opts = ConfigOptsIter("foo");
assert_eq!(opts.try_next(), Ok(Some("foo")));
assert_eq!(opts.try_next(), Ok(None));
}
#[test]
fn options_no_comma_quoted() {
let mut opts = ConfigOptsIter("foo=\"bar\"");
assert_eq!(opts.try_next(), Ok(Some("foo=\"bar\"")));
assert_eq!(opts.try_next(), Ok(None));
// Comma inside quoted section
let mut opts = ConfigOptsIter("foo=\"bar,baz\"");
assert_eq!(opts.try_next(), Ok(Some("foo=\"bar,baz\"")));
assert_eq!(opts.try_next(), Ok(None));
}
#[test]
fn options_comma_delimited() {
let mut opts = ConfigOptsIter("foo,bar");
assert_eq!(opts.try_next(), Ok(Some("foo")));
assert_eq!(opts.try_next(), Ok(Some("bar")));
assert_eq!(opts.try_next(), Ok(None));
}
#[test]
fn options_comma_delimited_quoted() {
let mut opts = ConfigOptsIter("foo=\"bar\",baz");
assert_eq!(opts.try_next(), Ok(Some("foo=\"bar\"")));
assert_eq!(opts.try_next(), Ok(Some("baz")));
assert_eq!(opts.try_next(), Ok(None));
}
#[test]
fn options_invalid_character() {
let mut opts = ConfigOptsIter("");
assert_eq!(
opts.try_next(),
Err(encoding::Error::CharacterEncoding.into())
);
let mut opts = ConfigOptsIter("x,❌");
assert_eq!(opts.try_next(), Ok(Some("x")));
assert_eq!(
opts.try_next(),
Err(encoding::Error::CharacterEncoding.into())
);
}
}

550
vendor/ssh-key/src/certificate.rs vendored Normal file
View File

@@ -0,0 +1,550 @@
//! OpenSSH certificate support.
mod builder;
mod cert_type;
mod field;
mod options_map;
mod unix_time;
pub use self::{builder::Builder, cert_type::CertType, field::Field, options_map::OptionsMap};
use self::unix_time::UnixTime;
use crate::{
public::{KeyData, SshFormat},
Algorithm, Error, Fingerprint, HashAlg, Result, Signature,
};
use alloc::{
borrow::ToOwned,
string::{String, ToString},
vec::Vec,
};
use core::str::FromStr;
use encoding::{Base64Reader, CheckedSum, Decode, Encode, Reader, Writer};
use signature::Verifier;
#[cfg(feature = "serde")]
use serde::{de, ser, Deserialize, Serialize};
#[cfg(feature = "std")]
use std::{fs, path::Path, time::SystemTime};
/// OpenSSH certificate as specified in [PROTOCOL.certkeys].
///
/// OpenSSH supports X.509-like certificate authorities, but using a custom
/// encoding format.
///
/// # ⚠️ Security Warning
///
/// Certificates must be validated before they can be trusted!
///
/// The [`Certificate`] type does not automatically perform validation checks
/// and supports parsing certificates which may potentially be invalid.
/// Just because a [`Certificate`] parses successfully does not mean that it
/// can be trusted.
///
/// See "Certificate Validation" documentation below for more information on
/// how to properly validate certificates.
///
/// # Certificate Validation
///
/// For a certificate to be trusted, the following properties MUST be
/// validated:
///
/// - Certificate is signed by a trusted certificate authority (CA)
/// - Signature over the certificate verifies successfully
/// - Current time is within the certificate's validity window
/// - Certificate authorizes the expected principal
/// - All critical extensions to the certificate are recognized and validate
/// successfully.
///
/// The [`Certificate::validate`] and [`Certificate::validate_at`] methods can
/// be used to validate a certificate.
///
/// ## Example
///
/// The following example walks through how to implement the steps outlined
/// above for validating a certificate:
///
#[cfg_attr(all(feature = "p256", feature = "std"), doc = " ```")]
#[cfg_attr(not(all(feature = "p256", feature = "std")), doc = " ```ignore")]
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use ssh_key::{Certificate, Fingerprint};
/// use std::str::FromStr;
///
/// // List of trusted certificate authority (CA) fingerprints
/// let ca_fingerprints = [
/// Fingerprint::from_str("SHA256:JQ6FV0rf7qqJHZqIj4zNH8eV0oB8KLKh9Pph3FTD98g")?
/// ];
///
/// // Certificate to be validated
/// let certificate = Certificate::from_str(
/// "ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIE7x9ln6uZLLkfXM8iatrnAAuytVHeCznU8VlEgx7TvLAAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAAAAAAAAAAAABAAAAFGVkMjU1MTktd2l0aC1wMjU2LWNhAAAAAAAAAABiUZm7AAAAAPTaMrsAAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR8H9hzDOU0V76NkkCY7DZIgw+SqoojY6xlb91FIfpjE+UR8YkbTp5ar44ULQatFaZqQlfz8FHYTooOL5G6gHBHAAAAZAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAASQAAACEA/0Cwxhkac5AeNYE958j8GgvmkIESDH1TE7QYIqxsFsIAAAAgTEw8WVjlz8AnvyaKGOUELMpyFFJagtD2JFAIAJvilrc= user@example.com"
/// )?;
///
/// // Perform basic certificate validation, ensuring that the certificate is
/// // signed by a trusted certificate authority (CA) and checking that the
/// // current system clock time is within the certificate's validity window
/// certificate.validate(&ca_fingerprints)?;
///
/// // Check that the certificate includes the expected principal name
/// // (i.e. username or hostname)
/// // if !certificate.principals().contains(expected_principal) { return Err(...) }
///
/// // Check that all of the critical extensions are recognized
/// // if !certificate.critical_options.iter().all(|critical| ...) { return Err(...) }
///
/// // If we've made it this far, the certificate can be trusted
/// Ok(())
/// # }
/// ```
///
/// # Certificate Builder (SSH CA support)
///
/// This crate implements all of the functionality needed for a pure Rust
/// SSH certificate authority which can build and sign OpenSSH certificates.
///
/// See the [`Builder`] type's documentation for more information.
///
/// # `serde` support
///
/// When the `serde` feature of this crate is enabled, this type receives impls
/// of [`Deserialize`][`serde::Deserialize`] and [`Serialize`][`serde::Serialize`].
///
/// The serialization uses a binary encoding with binary formats like bincode
/// and CBOR, and the OpenSSH string serialization when used with
/// human-readable formats like JSON and TOML.
///
/// [PROTOCOL.certkeys]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Certificate {
/// CA-provided random bitstring of arbitrary length
/// (but typically 16 or 32 bytes).
nonce: Vec<u8>,
/// Public key data.
public_key: KeyData,
/// Serial number.
serial: u64,
/// Certificate type.
cert_type: CertType,
/// Key ID.
key_id: String,
/// Valid principals.
valid_principals: Vec<String>,
/// Valid after.
valid_after: UnixTime,
/// Valid before.
valid_before: UnixTime,
/// Critical options.
critical_options: OptionsMap,
/// Extensions.
extensions: OptionsMap,
/// Reserved field.
reserved: Vec<u8>,
/// Signature key of signing CA.
signature_key: KeyData,
/// Signature over the certificate.
signature: Signature,
/// Comment on the certificate.
comment: String,
}
impl Certificate {
/// Parse an OpenSSH-formatted certificate.
///
/// OpenSSH-formatted certificates look like the following
/// (i.e. similar to OpenSSH public keys with `-cert-v01@openssh.com`):
///
/// ```text
/// ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlc...8REbCaAw== user@example.com
/// ```
pub fn from_openssh(certificate_str: &str) -> Result<Self> {
let encapsulation = SshFormat::decode(certificate_str.trim_end().as_bytes())?;
let mut reader = Base64Reader::new(encapsulation.base64_data)?;
let mut cert = Certificate::decode(&mut reader)?;
// Verify that the algorithm in the Base64-encoded data matches the text
if encapsulation.algorithm_id != cert.algorithm().to_certificate_type() {
return Err(Error::AlgorithmUnknown);
}
cert.comment = encapsulation.comment.to_owned();
Ok(reader.finish(cert)?)
}
/// Parse a raw binary OpenSSH certificate.
pub fn from_bytes(mut bytes: &[u8]) -> Result<Self> {
let reader = &mut bytes;
let cert = Certificate::decode(reader)?;
Ok(reader.finish(cert)?)
}
/// Encode OpenSSH certificate to a [`String`].
pub fn to_openssh(&self) -> Result<String> {
SshFormat::encode_string(
&self.algorithm().to_certificate_type(),
self,
self.comment(),
)
}
/// Serialize OpenSSH certificate as raw bytes.
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let mut cert_bytes = Vec::new();
self.encode(&mut cert_bytes)?;
Ok(cert_bytes)
}
/// Read OpenSSH certificate from a file.
#[cfg(feature = "std")]
pub fn read_file(path: &Path) -> Result<Self> {
let input = fs::read_to_string(path)?;
Self::from_openssh(&input)
}
/// Write OpenSSH certificate to a file.
#[cfg(feature = "std")]
pub fn write_file(&self, path: &Path) -> Result<()> {
let encoded = self.to_openssh()?;
fs::write(path, encoded.as_bytes())?;
Ok(())
}
/// Get the public key algorithm for this certificate.
pub fn algorithm(&self) -> Algorithm {
self.public_key.algorithm()
}
/// Get the comment on this certificate.
pub fn comment(&self) -> &str {
self.comment.as_str()
}
/// Nonces are a CA-provided random bitstring of arbitrary length
/// (but typically 16 or 32 bytes).
///
/// It's included to make attacks that depend on inducing collisions in the
/// signature hash infeasible.
pub fn nonce(&self) -> &[u8] {
&self.nonce
}
/// Get this certificate's public key data.
pub fn public_key(&self) -> &KeyData {
&self.public_key
}
/// Optional certificate serial number set by the CA to provide an
/// abbreviated way to refer to certificates from that CA.
///
/// If a CA does not wish to number its certificates, it must set this
/// field to zero.
pub fn serial(&self) -> u64 {
self.serial
}
/// Specifies whether this certificate is for identification of a user or
/// a host.
pub fn cert_type(&self) -> CertType {
self.cert_type
}
/// Key IDs are a free-form text field that is filled in by the CA at the
/// time of signing.
///
/// The intention is that the contents of this field are used to identify
/// the identity principal in log messages.
pub fn key_id(&self) -> &str {
&self.key_id
}
/// List of zero or more principals which this certificate is valid for.
///
/// Principals are hostnames for host certificates and usernames for user
/// certificates.
///
/// As a special case, a zero-length "valid principals" field means the
/// certificate is valid for any principal of the specified type.
pub fn valid_principals(&self) -> &[String] {
&self.valid_principals
}
/// Valid after (Unix time).
pub fn valid_after(&self) -> u64 {
self.valid_after.into()
}
/// Valid before (Unix time).
pub fn valid_before(&self) -> u64 {
self.valid_before.into()
}
/// Valid after (system time).
#[cfg(feature = "std")]
pub fn valid_after_time(&self) -> SystemTime {
self.valid_after.into()
}
/// Valid before (system time).
#[cfg(feature = "std")]
pub fn valid_before_time(&self) -> SystemTime {
self.valid_before.into()
}
/// The critical options section of the certificate specifies zero or more
/// options on the certificate's validity.
///
/// Each named option may only appear once in a certificate.
///
/// All options are "critical"; if an implementation does not recognize an
/// option, then the validating party should refuse to accept the
/// certificate.
pub fn critical_options(&self) -> &OptionsMap {
&self.critical_options
}
/// The extensions section of the certificate specifies zero or more
/// non-critical certificate extensions.
///
/// If an implementation does not recognise an extension, then it should
/// ignore it.
pub fn extensions(&self) -> &OptionsMap {
&self.extensions
}
/// Signature key of signing CA.
pub fn signature_key(&self) -> &KeyData {
&self.signature_key
}
/// Signature computed over all preceding fields from the initial string up
/// to, and including the signature key.
pub fn signature(&self) -> &Signature {
&self.signature
}
/// Perform certificate validation using the system clock to check that
/// the current time is within the certificate's validity window.
///
/// # ⚠️ Security Warning: Some Assembly Required
///
/// See [`Certificate::validate_at`] documentation for important notes on
/// how to properly validate certificates!
#[cfg(feature = "std")]
pub fn validate<'a, I>(&self, ca_fingerprints: I) -> Result<()>
where
I: IntoIterator<Item = &'a Fingerprint>,
{
self.validate_at(UnixTime::now()?.into(), ca_fingerprints)
}
/// Perform certificate validation.
///
/// Checks for the following:
///
/// - Specified Unix timestamp is within the certificate's valid range
/// - Certificate's signature validates against the public key included in
/// the certificate
/// - Fingerprint of the public key included in the certificate matches one
/// of the trusted certificate authority (CA) fingerprints provided in
/// the `ca_fingerprints` parameter.
///
/// NOTE: only SHA-256 fingerprints are supported at this time.
///
/// # ⚠️ Security Warning: Some Assembly Required
///
/// This method does not perform the full set of validation checks needed
/// to determine if a certificate is to be trusted.
///
/// If this method succeeds, the following properties still need to be
/// checked to ensure the certificate is valid:
///
/// - `valid_principals` is empty or contains the expected principal
/// - `critical_options` is empty or contains *only* options which are
/// recognized, and that the recognized options are all valid
///
/// ## Returns
/// - `Ok` if the certificate validated successfully
/// - `Error::CertificateValidation` if the certificate failed to validate
pub fn validate_at<'a, I>(&self, unix_timestamp: u64, ca_fingerprints: I) -> Result<()>
where
I: IntoIterator<Item = &'a Fingerprint>,
{
self.verify_signature()?;
// TODO(tarcieri): support non SHA-256 public key fingerprints?
let cert_fingerprint = self.signature_key.fingerprint(HashAlg::Sha256);
if !ca_fingerprints.into_iter().any(|f| f == &cert_fingerprint) {
return Err(Error::CertificateValidation);
}
let unix_timestamp = UnixTime::new(unix_timestamp)?;
// From PROTOCOL.certkeys:
//
// "valid after" and "valid before" specify a validity period for the
// certificate. Each represents a time in seconds since 1970-01-01
// A certificate is considered valid if:
//
// valid after <= current time < valid before
if self.valid_after <= unix_timestamp && unix_timestamp < self.valid_before {
Ok(())
} else {
Err(Error::CertificateValidation)
}
}
/// Verify the signature on the certificate against the public key in the
/// certificate.
///
/// # ⚠️ Security Warning
///
/// DON'T USE THIS!
///
/// This function alone does not provide any security guarantees whatsoever.
///
/// It verifies the signature in the certificate matches the CA public key
/// in the certificate, but does not ensure the CA is trusted.
///
/// It is public only for testing purposes, and deliberately hidden from
/// the documentation for that reason.
#[doc(hidden)]
pub fn verify_signature(&self) -> Result<()> {
let mut tbs_certificate = Vec::new();
self.encode_tbs(&mut tbs_certificate)?;
self.signature_key
.verify(&tbs_certificate, &self.signature)
.map_err(|_| Error::CertificateValidation)
}
/// Encode the portion of the certificate "to be signed" by the CA
/// (or to be verified against an existing CA signature)
fn encode_tbs(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.algorithm().to_certificate_type().encode(writer)?;
self.nonce.encode(writer)?;
self.public_key.encode_key_data(writer)?;
self.serial.encode(writer)?;
self.cert_type.encode(writer)?;
self.key_id.encode(writer)?;
self.valid_principals.encode(writer)?;
self.valid_after.encode(writer)?;
self.valid_before.encode(writer)?;
self.critical_options.encode(writer)?;
self.extensions.encode(writer)?;
self.reserved.encode(writer)?;
self.signature_key.encode_prefixed(writer)
}
}
impl Decode for Certificate {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let algorithm = Algorithm::new_certificate(&String::decode(reader)?)?;
Ok(Self {
nonce: Vec::decode(reader)?,
public_key: KeyData::decode_as(reader, algorithm)?,
serial: u64::decode(reader)?,
cert_type: CertType::decode(reader)?,
key_id: String::decode(reader)?,
valid_principals: Vec::decode(reader)?,
valid_after: UnixTime::decode(reader)?,
valid_before: UnixTime::decode(reader)?,
critical_options: OptionsMap::decode(reader)?,
extensions: OptionsMap::decode(reader)?,
reserved: Vec::decode(reader)?,
signature_key: reader.read_prefixed(KeyData::decode)?,
signature: reader.read_prefixed(Signature::decode)?,
comment: String::new(),
})
}
}
impl Encode for Certificate {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.algorithm().to_certificate_type().encoded_len()?,
self.nonce.encoded_len()?,
self.public_key.encoded_key_data_len()?,
self.serial.encoded_len()?,
self.cert_type.encoded_len()?,
self.key_id.encoded_len()?,
self.valid_principals.encoded_len()?,
self.valid_after.encoded_len()?,
self.valid_before.encoded_len()?,
self.critical_options.encoded_len()?,
self.extensions.encoded_len()?,
self.reserved.encoded_len()?,
self.signature_key.encoded_len_prefixed()?,
self.signature.encoded_len_prefixed()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.encode_tbs(writer)?;
self.signature.encode_prefixed(writer)
}
}
impl FromStr for Certificate {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::from_openssh(s)
}
}
impl ToString for Certificate {
fn to_string(&self) -> String {
self.to_openssh().expect("SSH certificate encoding error")
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Certificate {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
if deserializer.is_human_readable() {
let string = String::deserialize(deserializer)?;
Self::from_openssh(&string).map_err(de::Error::custom)
} else {
let bytes = Vec::<u8>::deserialize(deserializer)?;
Self::from_bytes(&bytes).map_err(de::Error::custom)
}
}
}
#[cfg(feature = "serde")]
impl Serialize for Certificate {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
if serializer.is_human_readable() {
self.to_openssh()
.map_err(ser::Error::custom)?
.serialize(serializer)
} else {
self.to_bytes()
.map_err(ser::Error::custom)?
.serialize(serializer)
}
}
}

View File

@@ -0,0 +1,313 @@
//! OpenSSH certificate builder.
use super::{unix_time::UnixTime, CertType, Certificate, Field, OptionsMap};
use crate::{public, Result, Signature, SigningKey};
use alloc::{string::String, vec::Vec};
#[cfg(feature = "rand_core")]
use rand_core::CryptoRngCore;
#[cfg(feature = "std")]
use std::time::SystemTime;
#[cfg(doc)]
use crate::PrivateKey;
/// OpenSSH certificate builder.
///
/// This type provides the core functionality of an OpenSSH certificate
/// authority.
///
/// It can build and sign OpenSSH certificates.
///
/// ## Principals
///
/// Certificates are valid for a specific set of principal names:
///
/// - Usernames for [`CertType::User`].
/// - Hostnames for [`CertType::Host`].
///
/// When building a certificate, you will either need to specify principals
/// by calling [`Builder::valid_principal`] one or more times, or explicitly
/// marking a certificate as valid for all principals (i.e. "golden ticket")
/// using the [`Builder::all_principals_valid`] method.
///
/// ## Example
///
#[cfg_attr(
all(feature = "ed25519", feature = "getrandom", feature = "std"),
doc = " ```"
)]
#[cfg_attr(
not(all(feature = "ed25519", feature = "getrandom", feature = "std")),
doc = " ```ignore"
)]
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use ssh_key::{Algorithm, PrivateKey, certificate, rand_core::OsRng};
/// use std::time::{SystemTime, UNIX_EPOCH};
///
/// // Generate the certificate authority's private key
/// let ca_key = PrivateKey::random(&mut OsRng, Algorithm::Ed25519)?;
///
/// // Generate a "subject" key to be signed by the certificate authority.
/// // Normally a user or host would do this locally and give the certificate
/// // authority the public key.
/// let subject_private_key = PrivateKey::random(&mut OsRng, Algorithm::Ed25519)?;
/// let subject_public_key = subject_private_key.public_key();
///
/// // Create certificate validity window
/// let valid_after = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
/// let valid_before = valid_after + (365 * 86400); // e.g. 1 year
///
/// // Initialize certificate builder
/// let mut cert_builder = certificate::Builder::new_with_random_nonce(
/// &mut OsRng,
/// subject_public_key,
/// valid_after,
/// valid_before,
/// )?;
/// cert_builder.serial(42)?; // Optional: serial number chosen by the CA
/// cert_builder.key_id("nobody-cert-02")?; // Optional: CA-specific key identifier
/// cert_builder.cert_type(certificate::CertType::User)?; // User or host certificate
/// cert_builder.valid_principal("nobody")?; // Unix username or hostname
/// cert_builder.comment("nobody@example.com")?; // Comment (typically an email address)
///
/// // Sign and return the `Certificate` for `subject_public_key`
/// let cert = cert_builder.sign(&ca_key)?;
/// # Ok(())
/// # }
/// ```
pub struct Builder {
public_key: public::KeyData,
nonce: Vec<u8>,
serial: Option<u64>,
cert_type: Option<CertType>,
key_id: Option<String>,
valid_principals: Option<Vec<String>>,
valid_after: UnixTime,
valid_before: UnixTime,
critical_options: OptionsMap,
extensions: OptionsMap,
comment: Option<String>,
}
impl Builder {
/// Recommended size for a nonce.
pub const RECOMMENDED_NONCE_SIZE: usize = 16;
/// Create a new certificate builder for the given subject's public key.
///
/// Also requires a nonce (random value typically 16 or 32 bytes long) and
/// the validity window of the certificate as Unix seconds.
pub fn new(
nonce: impl Into<Vec<u8>>,
public_key: impl Into<public::KeyData>,
valid_after: u64,
valid_before: u64,
) -> Result<Self> {
let valid_after =
UnixTime::new(valid_after).map_err(|_| Field::ValidAfter.invalid_error())?;
let valid_before =
UnixTime::new(valid_before).map_err(|_| Field::ValidBefore.invalid_error())?;
if valid_before < valid_after {
return Err(Field::ValidBefore.invalid_error());
}
Ok(Self {
nonce: nonce.into(),
public_key: public_key.into(),
serial: None,
cert_type: None,
key_id: None,
valid_principals: None,
valid_after,
valid_before,
critical_options: OptionsMap::new(),
extensions: OptionsMap::new(),
comment: None,
})
}
/// Create a new certificate builder with the validity window specified
/// using [`SystemTime`] values.
#[cfg(feature = "std")]
pub fn new_with_validity_times(
nonce: impl Into<Vec<u8>>,
public_key: impl Into<public::KeyData>,
valid_after: SystemTime,
valid_before: SystemTime,
) -> Result<Self> {
let valid_after =
UnixTime::try_from(valid_after).map_err(|_| Field::ValidAfter.invalid_error())?;
let valid_before =
UnixTime::try_from(valid_before).map_err(|_| Field::ValidBefore.invalid_error())?;
Self::new(nonce, public_key, valid_after.into(), valid_before.into())
}
/// Create a new certificate builder, generating a random nonce using the
/// provided random number generator.
#[cfg(feature = "rand_core")]
pub fn new_with_random_nonce(
rng: &mut impl CryptoRngCore,
public_key: impl Into<public::KeyData>,
valid_after: u64,
valid_before: u64,
) -> Result<Self> {
let mut nonce = vec![0u8; Self::RECOMMENDED_NONCE_SIZE];
rng.fill_bytes(&mut nonce);
Self::new(nonce, public_key, valid_after, valid_before)
}
/// Set certificate serial number.
///
/// Default: `0`.
pub fn serial(&mut self, serial: u64) -> Result<&mut Self> {
if self.serial.is_some() {
return Err(Field::Serial.invalid_error());
}
self.serial = Some(serial);
Ok(self)
}
/// Set certificate type: user or host.
///
/// Default: [`CertType::User`].
pub fn cert_type(&mut self, cert_type: CertType) -> Result<&mut Self> {
if self.cert_type.is_some() {
return Err(Field::Type.invalid_error());
}
self.cert_type = Some(cert_type);
Ok(self)
}
/// Set key ID: label to identify this particular certificate.
///
/// Default `""`
pub fn key_id(&mut self, key_id: impl Into<String>) -> Result<&mut Self> {
if self.key_id.is_some() {
return Err(Field::KeyId.invalid_error());
}
self.key_id = Some(key_id.into());
Ok(self)
}
/// Add a principal (i.e. username or hostname) to `valid_principals`.
pub fn valid_principal(&mut self, principal: impl Into<String>) -> Result<&mut Self> {
match &mut self.valid_principals {
Some(principals) => principals.push(principal.into()),
None => self.valid_principals = Some(vec![principal.into()]),
}
Ok(self)
}
/// Mark this certificate as being valid for all principals.
///
/// # ⚠️ Security Warning
///
/// Use this method with care! It generates "golden ticket" certificates
/// which can e.g. authenticate as any user on a system, or impersonate
/// any host.
pub fn all_principals_valid(&mut self) -> Result<&mut Self> {
self.valid_principals = Some(Vec::new());
Ok(self)
}
/// Add a critical option to this certificate.
///
/// Critical options must be recognized or the certificate must be rejected.
pub fn critical_option(
&mut self,
name: impl Into<String>,
data: impl Into<String>,
) -> Result<&mut Self> {
let name = name.into();
let data = data.into();
if self.critical_options.contains_key(&name) {
return Err(Field::CriticalOptions.invalid_error());
}
self.critical_options.insert(name, data);
Ok(self)
}
/// Add an extension to this certificate.
///
/// Extensions can be unrecognized without impacting the certificate.
pub fn extension(
&mut self,
name: impl Into<String>,
data: impl Into<String>,
) -> Result<&mut Self> {
let name = name.into();
let data = data.into();
if self.extensions.contains_key(&name) {
return Err(Field::Extensions.invalid_error());
}
self.extensions.insert(name, data);
Ok(self)
}
/// Add a comment to this certificate.
///
/// Default `""`
pub fn comment(&mut self, comment: impl Into<String>) -> Result<&mut Self> {
if self.comment.is_some() {
return Err(Field::Comment.invalid_error());
}
self.comment = Some(comment.into());
Ok(self)
}
/// Sign the certificate using the provided signer type.
///
/// The [`PrivateKey`] type can be used as a signer.
pub fn sign<S: SigningKey>(self, signing_key: &S) -> Result<Certificate> {
// Empty valid principals result in a "golden ticket", so this check
// ensures that was explicitly configured via `all_principals_valid`.
let valid_principals = match self.valid_principals {
Some(principals) => principals,
None => return Err(Field::ValidPrincipals.invalid_error()),
};
let mut cert = Certificate {
nonce: self.nonce,
public_key: self.public_key,
serial: self.serial.unwrap_or_default(),
cert_type: self.cert_type.unwrap_or_default(),
key_id: self.key_id.unwrap_or_default(),
valid_principals,
valid_after: self.valid_after,
valid_before: self.valid_before,
critical_options: self.critical_options,
extensions: self.extensions,
reserved: Vec::new(),
comment: self.comment.unwrap_or_default(),
signature_key: signing_key.public_key(),
signature: Signature::placeholder(),
};
let mut tbs_cert = Vec::new();
cert.encode_tbs(&mut tbs_cert)?;
cert.signature = signing_key.try_sign(&tbs_cert)?;
#[cfg(debug_assertions)]
cert.validate_at(
cert.valid_after.into(),
&[cert.signature_key.fingerprint(Default::default())],
)?;
Ok(cert)
}
}

View File

@@ -0,0 +1,70 @@
//! OpenSSH certificate types.
use crate::{Error, Result};
use encoding::{Decode, Encode, Reader, Writer};
/// Types of OpenSSH certificates: user or host.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum CertType {
/// User certificate
User = 1,
/// Host certificate
Host = 2,
}
impl CertType {
/// Is this a host certificate?
pub fn is_host(self) -> bool {
self == CertType::Host
}
/// Is this a user certificate?
pub fn is_user(self) -> bool {
self == CertType::User
}
}
impl Default for CertType {
fn default() -> Self {
Self::User
}
}
impl Decode for CertType {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
u32::decode(reader)?.try_into()
}
}
impl Encode for CertType {
fn encoded_len(&self) -> encoding::Result<usize> {
Ok(4)
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
u32::from(*self).encode(writer)?;
Ok(())
}
}
impl From<CertType> for u32 {
fn from(cert_type: CertType) -> u32 {
cert_type as u32
}
}
impl TryFrom<u32> for CertType {
type Error = Error;
fn try_from(n: u32) -> Result<CertType> {
match n {
1 => Ok(CertType::User),
2 => Ok(CertType::Host),
_ => Err(Error::FormatEncoding),
}
}
}

88
vendor/ssh-key/src/certificate/field.rs vendored Normal file
View File

@@ -0,0 +1,88 @@
//! Certificate fields.
use crate::Error;
use core::fmt;
/// Certificate fields.
///
/// This type is primarily used by the certificate builder for reporting
/// errors in certificates.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum Field {
/// Subject public key
PublicKey,
/// Nonce
Nonce,
/// Serial number
Serial,
/// Certificate type: user or host
Type,
/// Key ID
KeyId,
/// Valid principals
ValidPrincipals,
/// Valid after (Unix time)
ValidAfter,
/// Valid before (Unix time)
ValidBefore,
/// Critical options
CriticalOptions,
/// Extensions
Extensions,
/// Signature key (i.e. CA key)
SignatureKey,
/// Signature
Signature,
/// Comment
Comment,
}
impl Field {
/// Get the field name as a string
pub fn as_str(self) -> &'static str {
match self {
Self::PublicKey => "public key",
Self::Nonce => "nonce",
Self::Serial => "serial",
Self::Type => "type",
Self::KeyId => "key id",
Self::ValidPrincipals => "valid principals",
Self::ValidAfter => "valid after",
Self::ValidBefore => "valid before",
Self::CriticalOptions => "critical options",
Self::Extensions => "extensions",
Self::SignatureKey => "signature key",
Self::Signature => "signature",
Self::Comment => "comment",
}
}
/// Get an [`Error`] that this field is invalid.
pub fn invalid_error(self) -> Error {
Error::CertificateFieldInvalid(self)
}
}
impl AsRef<str> for Field {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for Field {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}

View File

@@ -0,0 +1,126 @@
//! OpenSSH certificate options used by critical options and extensions.
use crate::{Error, Result};
use alloc::{collections::BTreeMap, string::String, vec::Vec};
use core::{
cmp::Ordering,
ops::{Deref, DerefMut},
};
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
/// Key/value map type used for certificate's critical options and extensions.
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)]
pub struct OptionsMap(pub BTreeMap<String, String>);
impl OptionsMap {
/// Create a new [`OptionsMap`].
pub fn new() -> Self {
Self::default()
}
}
impl Deref for OptionsMap {
type Target = BTreeMap<String, String>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for OptionsMap {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Decode for OptionsMap {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
reader.read_prefixed(|reader| {
let mut entries = Vec::<(String, String)>::new();
while !reader.is_finished() {
let name = String::decode(reader)?;
let data = reader.read_prefixed(|reader| {
if reader.remaining_len() > 0 {
String::decode(reader)
} else {
Ok(String::default())
}
})?;
// Options must be lexically ordered by "name" if they appear in
// the sequence. Each named option may only appear once in a
// certificate.
if let Some((prev_name, _)) = entries.last() {
if prev_name.cmp(&name) != Ordering::Less {
return Err(Error::FormatEncoding);
}
}
entries.push((name, data));
}
Ok(OptionsMap::from_iter(entries))
})
}
}
impl Encode for OptionsMap {
fn encoded_len(&self) -> encoding::Result<usize> {
self.iter()
.try_fold(4, |acc, (name, data)| {
[
acc,
name.encoded_len()?,
if data.is_empty() {
4
} else {
data.encoded_len_prefixed()?
},
]
.checked_sum()
})
.map_err(Into::into)
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.encoded_len()?
.checked_sub(4)
.ok_or(encoding::Error::Length)?
.encode(writer)?;
for (name, data) in self.iter() {
name.encode(writer)?;
if data.is_empty() {
0usize.encode(writer)?;
} else {
data.encode_prefixed(writer)?
}
}
Ok(())
}
}
impl From<BTreeMap<String, String>> for OptionsMap {
fn from(map: BTreeMap<String, String>) -> OptionsMap {
OptionsMap(map)
}
}
impl From<OptionsMap> for BTreeMap<String, String> {
fn from(map: OptionsMap) -> BTreeMap<String, String> {
map.0
}
}
impl FromIterator<(String, String)> for OptionsMap {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = (String, String)>,
{
BTreeMap::from_iter(iter).into()
}
}

View File

@@ -0,0 +1,7 @@
//! Certificate signing key trait.
use crate::{public, Signature};
use signature::Signer;
#[cfg(doc)]
use super::Builder;

View File

@@ -0,0 +1,132 @@
//! Unix timestamps.
use crate::{Error, Result};
use core::fmt;
use core::fmt::Formatter;
use encoding::{Decode, Encode, Reader, Writer};
#[cfg(feature = "std")]
use std::time::{Duration, SystemTime, UNIX_EPOCH};
/// Maximum allowed value for a Unix timestamp.
pub const MAX_SECS: u64 = i64::MAX as u64;
/// Unix timestamps as used in OpenSSH certificates.
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
pub(super) struct UnixTime {
/// Number of seconds since the Unix epoch
secs: u64,
/// System time corresponding to this Unix timestamp
#[cfg(feature = "std")]
time: SystemTime,
}
impl UnixTime {
/// Create a new Unix timestamp.
///
/// `secs` is the number of seconds since the Unix epoch and must be less
/// than or equal to `i64::MAX`.
#[cfg(not(feature = "std"))]
pub fn new(secs: u64) -> Result<Self> {
if secs <= MAX_SECS {
Ok(Self { secs })
} else {
Err(Error::Time)
}
}
/// Create a new Unix timestamp.
///
/// This version requires `std` and ensures there's a valid `SystemTime`
/// representation with an infallible conversion (which also improves the
/// `Debug` output)
#[cfg(feature = "std")]
pub fn new(secs: u64) -> Result<Self> {
if secs > MAX_SECS {
return Err(Error::Time);
}
match UNIX_EPOCH.checked_add(Duration::from_secs(secs)) {
Some(time) => Ok(Self { secs, time }),
None => Err(Error::Time),
}
}
/// Get the current time as a Unix timestamp.
#[cfg(feature = "std")]
pub fn now() -> Result<Self> {
SystemTime::now().try_into()
}
}
impl Decode for UnixTime {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
u64::decode(reader)?.try_into()
}
}
impl Encode for UnixTime {
fn encoded_len(&self) -> encoding::Result<usize> {
self.secs.encoded_len()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.secs.encode(writer)?;
Ok(())
}
}
impl From<UnixTime> for u64 {
fn from(unix_time: UnixTime) -> u64 {
unix_time.secs
}
}
#[cfg(feature = "std")]
impl From<UnixTime> for SystemTime {
fn from(unix_time: UnixTime) -> SystemTime {
unix_time.time
}
}
impl TryFrom<u64> for UnixTime {
type Error = Error;
fn try_from(unix_secs: u64) -> Result<UnixTime> {
Self::new(unix_secs)
}
}
#[cfg(feature = "std")]
impl TryFrom<SystemTime> for UnixTime {
type Error = Error;
fn try_from(time: SystemTime) -> Result<UnixTime> {
Self::new(time.duration_since(UNIX_EPOCH)?.as_secs())
}
}
impl fmt::Debug for UnixTime {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.secs)
}
}
#[cfg(test)]
mod tests {
use super::{UnixTime, MAX_SECS};
use crate::Error;
#[test]
fn new_with_max_secs() {
assert!(UnixTime::new(MAX_SECS).is_ok());
}
#[test]
fn new_over_max_secs_returns_error() {
assert_eq!(UnixTime::new(MAX_SECS + 1), Err(Error::Time));
}
}

237
vendor/ssh-key/src/error.rs vendored Normal file
View File

@@ -0,0 +1,237 @@
//! Error types
use crate::Algorithm;
use core::fmt;
#[cfg(feature = "alloc")]
use crate::certificate;
/// Result type with `ssh-key`'s [`Error`] as the error type.
pub type Result<T> = core::result::Result<T, Error>;
/// Error type.
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Error {
/// Unknown algorithm.
///
/// This is returned when an algorithm is completely unknown to this crate.
AlgorithmUnknown,
/// Unsupported algorithm.
///
/// This is typically returned when an algorithm is recognized, but the
/// relevant crate features to support it haven't been enabled.
///
/// It may also be returned in the event an algorithm is inappropriate for
/// a given usage pattern or context.
AlgorithmUnsupported {
/// Algorithm identifier.
algorithm: Algorithm,
},
/// Certificate field is invalid or already set.
#[cfg(feature = "alloc")]
CertificateFieldInvalid(certificate::Field),
/// Certificate validation failed.
CertificateValidation,
/// Cryptographic errors.
Crypto,
/// Cannot perform operation on decrypted private key.
Decrypted,
/// ECDSA key encoding errors.
#[cfg(feature = "ecdsa")]
Ecdsa(sec1::Error),
/// Encoding errors.
Encoding(encoding::Error),
/// Cannot perform operation on encrypted private key.
Encrypted,
/// Other format encoding errors.
FormatEncoding,
/// Input/output errors.
#[cfg(feature = "std")]
Io(std::io::ErrorKind),
/// Namespace invalid.
Namespace,
/// Public key is incorrect.
PublicKey,
/// Invalid timestamp (e.g. in a certificate)
Time,
/// Unexpected trailing data at end of message.
TrailingData {
/// Number of bytes of remaining data at end of message.
remaining: usize,
},
/// Unsupported version.
Version {
/// Version number.
number: u32,
},
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::AlgorithmUnknown => write!(f, "unknown algorithm"),
Error::AlgorithmUnsupported { algorithm } => {
write!(f, "unsupported algorithm: {algorithm}")
}
#[cfg(feature = "alloc")]
Error::CertificateFieldInvalid(field) => {
write!(f, "certificate field invalid: {field}")
}
Error::CertificateValidation => write!(f, "certificate validation failed"),
Error::Crypto => write!(f, "cryptographic error"),
Error::Decrypted => write!(f, "private key is already decrypted"),
#[cfg(feature = "ecdsa")]
Error::Ecdsa(err) => write!(f, "ECDSA encoding error: {err}"),
Error::Encoding(err) => write!(f, "{err}"),
Error::Encrypted => write!(f, "private key is encrypted"),
Error::FormatEncoding => write!(f, "format encoding error"),
#[cfg(feature = "std")]
Error::Io(err) => write!(f, "I/O error: {}", std::io::Error::from(*err)),
Error::Namespace => write!(f, "namespace invalid"),
Error::PublicKey => write!(f, "public key is incorrect"),
Error::Time => write!(f, "invalid time"),
Error::TrailingData { remaining } => write!(
f,
"unexpected trailing data at end of message ({remaining} bytes)",
),
Error::Version { number: version } => write!(f, "version unsupported: {version}"),
}
}
}
impl From<cipher::Error> for Error {
fn from(_: cipher::Error) -> Error {
Error::Crypto
}
}
impl From<core::array::TryFromSliceError> for Error {
fn from(_: core::array::TryFromSliceError) -> Error {
Error::Encoding(encoding::Error::Length)
}
}
impl From<core::str::Utf8Error> for Error {
fn from(err: core::str::Utf8Error) -> Error {
Error::Encoding(err.into())
}
}
impl From<encoding::Error> for Error {
fn from(err: encoding::Error) -> Error {
Error::Encoding(err)
}
}
impl From<encoding::LabelError> for Error {
fn from(err: encoding::LabelError) -> Error {
Error::Encoding(err.into())
}
}
impl From<encoding::base64::Error> for Error {
fn from(err: encoding::base64::Error) -> Error {
Error::Encoding(err.into())
}
}
impl From<encoding::pem::Error> for Error {
fn from(err: encoding::pem::Error) -> Error {
Error::Encoding(err.into())
}
}
#[cfg(not(feature = "std"))]
impl From<signature::Error> for Error {
fn from(_: signature::Error) -> Error {
Error::Crypto
}
}
#[cfg(feature = "std")]
impl From<signature::Error> for Error {
fn from(err: signature::Error) -> Error {
use std::error::Error as _;
err.source()
.and_then(|source| source.downcast_ref().cloned())
.unwrap_or(Error::Crypto)
}
}
#[cfg(not(feature = "std"))]
impl From<Error> for signature::Error {
fn from(_: Error) -> signature::Error {
signature::Error::new()
}
}
#[cfg(feature = "std")]
impl From<Error> for signature::Error {
fn from(err: Error) -> signature::Error {
signature::Error::from_source(err)
}
}
#[cfg(feature = "alloc")]
impl From<alloc::string::FromUtf8Error> for Error {
fn from(err: alloc::string::FromUtf8Error) -> Error {
Error::Encoding(err.into())
}
}
#[cfg(feature = "ecdsa")]
impl From<sec1::Error> for Error {
fn from(err: sec1::Error) -> Error {
Error::Ecdsa(err)
}
}
#[cfg(feature = "rsa")]
impl From<rsa::errors::Error> for Error {
fn from(_: rsa::errors::Error) -> Error {
Error::Crypto
}
}
#[cfg(feature = "std")]
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Error {
Error::Io(err.kind())
}
}
#[cfg(feature = "std")]
impl From<std::time::SystemTimeError> for Error {
fn from(_: std::time::SystemTimeError) -> Error {
Error::Time
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
#[cfg(feature = "ecdsa")]
Self::Ecdsa(err) => Some(err),
Self::Encoding(err) => Some(err),
_ => None,
}
}
}

221
vendor/ssh-key/src/fingerprint.rs vendored Normal file
View File

@@ -0,0 +1,221 @@
//! SSH public key fingerprints.
mod randomart;
use self::randomart::Randomart;
use crate::{public, Error, HashAlg, Result};
use core::{
fmt::{self, Display},
str::{self, FromStr},
};
use encoding::{
base64::{Base64Unpadded, Encoding},
Encode,
};
use sha2::{Digest, Sha256, Sha512};
/// Fingerprint encoding error message.
const FINGERPRINT_ERR_MSG: &str = "fingerprint encoding error";
#[cfg(feature = "alloc")]
use alloc::string::{String, ToString};
#[cfg(all(feature = "alloc", feature = "serde"))]
use serde::{de, ser, Deserialize, Serialize};
/// SSH public key fingerprints.
///
/// Fingerprints have an associated key fingerprint algorithm, i.e. a hash
/// function which is used to compute the fingerprint.
///
/// # Parsing/serializing fingerprint strings
///
/// The [`FromStr`] and [`Display`] impls on [`Fingerprint`] can be used to
/// parse and serialize fingerprints from the string format.
///
/// ### Example
///
/// ```text
/// SHA256:Nh0Me49Zh9fDw/VYUfq43IJmI1T+XrjiYONPND8GzaM
/// ```
///
/// # `serde` support
///
/// When the `serde` feature of this crate is enabled, this type receives impls
/// of [`Deserialize`][`serde::Deserialize`] and [`Serialize`][`serde::Serialize`].
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Fingerprint {
/// Fingerprints computed using SHA-256.
Sha256([u8; HashAlg::Sha256.digest_size()]),
/// Fingerprints computed using SHA-512.
Sha512([u8; HashAlg::Sha512.digest_size()]),
}
impl Fingerprint {
/// Size of a SHA-512 hash encoded as Base64.
const SHA512_BASE64_SIZE: usize = 86;
/// Create a fingerprint of the given public key data using the provided
/// hash algorithm.
pub fn new(algorithm: HashAlg, public_key: &public::KeyData) -> Self {
match algorithm {
HashAlg::Sha256 => {
let mut digest = Sha256::new();
public_key.encode(&mut digest).expect(FINGERPRINT_ERR_MSG);
Self::Sha256(digest.finalize().into())
}
HashAlg::Sha512 => {
let mut digest = Sha512::new();
public_key.encode(&mut digest).expect(FINGERPRINT_ERR_MSG);
Self::Sha512(digest.finalize().into())
}
}
}
/// Get the hash algorithm used for this fingerprint.
pub fn algorithm(self) -> HashAlg {
match self {
Self::Sha256(_) => HashAlg::Sha256,
Self::Sha512(_) => HashAlg::Sha512,
}
}
/// Get the name of the hash algorithm (upper case e.g. "SHA256").
pub fn prefix(self) -> &'static str {
match self.algorithm() {
HashAlg::Sha256 => "SHA256",
HashAlg::Sha512 => "SHA512",
}
}
/// Get the bracketed hash algorithm footer for use in "randomart".
fn footer(self) -> &'static str {
match self.algorithm() {
HashAlg::Sha256 => "[SHA256]",
HashAlg::Sha512 => "[SHA512]",
}
}
/// Get the raw digest output for the fingerprint as bytes.
pub fn as_bytes(&self) -> &[u8] {
match self {
Self::Sha256(bytes) => bytes.as_slice(),
Self::Sha512(bytes) => bytes.as_slice(),
}
}
/// Get the SHA-256 fingerprint, if this is one.
pub fn sha256(self) -> Option<[u8; HashAlg::Sha256.digest_size()]> {
match self {
Self::Sha256(fingerprint) => Some(fingerprint),
_ => None,
}
}
/// Get the SHA-512 fingerprint, if this is one.
pub fn sha512(self) -> Option<[u8; HashAlg::Sha512.digest_size()]> {
match self {
Self::Sha512(fingerprint) => Some(fingerprint),
_ => None,
}
}
/// Is this fingerprint SHA-256?
pub fn is_sha256(self) -> bool {
matches!(self, Self::Sha256(_))
}
/// Is this fingerprint SHA-512?
pub fn is_sha512(self) -> bool {
matches!(self, Self::Sha512(_))
}
/// Format "randomart" for this fingerprint using the provided formatter.
pub fn fmt_randomart(self, header: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Randomart::new(header, self).fmt(f)
}
/// Render "randomart" hash visualization for this fingerprint as a string.
///
/// ```text
/// +--[ED25519 256]--+
/// |o+oO==+ o.. |
/// |.o++Eo+o.. |
/// |. +.oO.o . . |
/// | . o..B.. . . |
/// | ...+ .S. o |
/// | .o. . . . . |
/// | o.. o |
/// | B . |
/// | .o* |
/// +----[SHA256]-----+
/// ```
#[cfg(feature = "alloc")]
pub fn to_randomart(self, header: &str) -> String {
Randomart::new(header, self).to_string()
}
}
impl AsRef<[u8]> for Fingerprint {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Display for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let prefix = self.prefix();
// Buffer size is the largest digest size of of any supported hash function
let mut buf = [0u8; Self::SHA512_BASE64_SIZE];
let base64 = Base64Unpadded::encode(self.as_bytes(), &mut buf).map_err(|_| fmt::Error)?;
write!(f, "{prefix}:{base64}")
}
}
impl FromStr for Fingerprint {
type Err = Error;
fn from_str(id: &str) -> Result<Self> {
let (alg_str, base64) = id.split_once(':').ok_or(Error::AlgorithmUnknown)?;
// Fingerprints use a special upper-case hash algorithm encoding.
let algorithm = match alg_str {
"SHA256" => HashAlg::Sha256,
"SHA512" => HashAlg::Sha512,
_ => return Err(Error::AlgorithmUnknown),
};
// Buffer size is the largest digest size of of any supported hash function
let mut buf = [0u8; HashAlg::Sha512.digest_size()];
let decoded_bytes = Base64Unpadded::decode(base64, &mut buf)?;
match algorithm {
HashAlg::Sha256 => Ok(Self::Sha256(decoded_bytes.try_into()?)),
HashAlg::Sha512 => Ok(Self::Sha512(decoded_bytes.try_into()?)),
}
}
}
#[cfg(all(feature = "alloc", feature = "serde"))]
impl<'de> Deserialize<'de> for Fingerprint {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
let string = String::deserialize(deserializer)?;
string.parse().map_err(de::Error::custom)
}
}
#[cfg(all(feature = "alloc", feature = "serde"))]
impl Serialize for Fingerprint {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
self.to_string().serialize(serializer)
}
}

View File

@@ -0,0 +1,113 @@
//! Support for the "drunken bishop" fingerprint algorithm, a.k.a. "randomart".
//!
//! The algorithm is described in the paper:
//!
//! "The drunken bishop: An analysis of the OpenSSH fingerprint visualization algorithm"
//!
//! <http://www.dirk-loss.de/sshvis/drunken_bishop.pdf>
use super::Fingerprint;
use core::fmt;
const WIDTH: usize = 17;
const HEIGHT: usize = 9;
const VALUES: &[u8; 17] = b" .o+=*BOX@%&#/^SE";
const NVALUES: u8 = VALUES.len() as u8 - 1;
type Field = [[u8; WIDTH]; HEIGHT];
/// "randomart" renderer.
pub(super) struct Randomart<'a> {
header: &'a str,
field: Field,
footer: &'static str,
}
impl<'a> Randomart<'a> {
/// Create new "randomart" from the given fingerprint.
// TODO: Remove this when the pipeline toolchain is updated beyond 1.69
#[allow(clippy::arithmetic_side_effects)]
pub(super) fn new(header: &'a str, fingerprint: Fingerprint) -> Self {
let mut field = Field::default();
let mut x = WIDTH / 2;
let mut y = HEIGHT / 2;
for mut byte in fingerprint.as_bytes().iter().copied() {
for _ in 0..4 {
if byte & 0x1 == 0 {
x = x.saturating_sub(1);
} else {
x = x.saturating_add(1);
}
if byte & 0x2 == 0 {
y = y.saturating_sub(1);
} else {
y = y.saturating_add(1);
}
x = x.min(WIDTH.saturating_sub(1));
y = y.min(HEIGHT.saturating_sub(1));
if field[y][x] < NVALUES - 2 {
field[y][x] = field[y][x].saturating_add(1);
}
byte >>= 2;
}
}
field[HEIGHT / 2][WIDTH / 2] = NVALUES - 1;
field[y][x] = NVALUES;
Self {
header,
field,
footer: fingerprint.footer(),
}
}
}
impl fmt::Display for Randomart<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "+{:-^width$}+", self.header, width = WIDTH)?;
for row in self.field {
write!(f, "|")?;
for c in row {
write!(f, "{}", VALUES[c as usize] as char)?;
}
writeln!(f, "|")?;
}
write!(f, "+{:-^width$}+", self.footer, width = WIDTH)
}
}
#[cfg(all(test, feature = "alloc"))]
mod tests {
use super::Fingerprint;
const EXAMPLE_FINGERPRINT: &str = "SHA256:UCUiLr7Pjs9wFFJMDByLgc3NrtdU344OgUM45wZPcIQ";
const EXAMPLE_RANDOMART: &str = "\
+--[ED25519 256]--+
|o+oO==+ o.. |
|.o++Eo+o.. |
|. +.oO.o . . |
| . o..B.. . . |
| ...+ .S. o |
| .o. . . . . |
| o.. o |
| B . |
| .o* |
+----[SHA256]-----+";
#[test]
fn generation() {
let fingerprint = EXAMPLE_FINGERPRINT.parse::<Fingerprint>().unwrap();
let randomart = fingerprint.to_randomart("[ED25519 256]");
assert_eq!(EXAMPLE_RANDOMART, randomart);
}
}

177
vendor/ssh-key/src/kdf.rs vendored Normal file
View File

@@ -0,0 +1,177 @@
//! Key Derivation Functions.
//!
//! These are used for deriving an encryption key from a password.
use crate::{Error, KdfAlg, Result};
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "encryption")]
use {crate::Cipher, bcrypt_pbkdf::bcrypt_pbkdf, rand_core::CryptoRngCore, zeroize::Zeroizing};
/// Default number of rounds to use for bcrypt-pbkdf.
#[cfg(feature = "encryption")]
const DEFAULT_BCRYPT_ROUNDS: u32 = 16;
/// Default salt size. Matches OpenSSH.
#[cfg(feature = "encryption")]
const DEFAULT_SALT_SIZE: usize = 16;
/// Key Derivation Functions (KDF).
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Kdf {
/// No KDF.
None,
/// bcrypt-pbkdf options.
#[cfg(feature = "alloc")]
Bcrypt {
/// Salt
salt: Vec<u8>,
/// Rounds
rounds: u32,
},
}
impl Kdf {
/// Initialize KDF configuration for the given algorithm.
#[cfg(feature = "encryption")]
pub fn new(algorithm: KdfAlg, rng: &mut impl CryptoRngCore) -> Result<Self> {
let mut salt = vec![0u8; DEFAULT_SALT_SIZE];
rng.fill_bytes(&mut salt);
match algorithm {
KdfAlg::None => {
// Disallow explicit initialization with a `none` algorithm
Err(Error::AlgorithmUnknown)
}
KdfAlg::Bcrypt => Ok(Kdf::Bcrypt {
salt,
rounds: DEFAULT_BCRYPT_ROUNDS,
}),
}
}
/// Get the KDF algorithm.
pub fn algorithm(&self) -> KdfAlg {
match self {
Self::None => KdfAlg::None,
#[cfg(feature = "alloc")]
Self::Bcrypt { .. } => KdfAlg::Bcrypt,
}
}
/// Derive an encryption key from the given password.
#[cfg(feature = "encryption")]
pub fn derive(&self, password: impl AsRef<[u8]>, output: &mut [u8]) -> Result<()> {
match self {
Kdf::None => Err(Error::Decrypted),
Kdf::Bcrypt { salt, rounds } => {
bcrypt_pbkdf(password, salt, *rounds, output).map_err(|_| Error::Crypto)?;
Ok(())
}
}
}
/// Derive key and IV for the given [`Cipher`].
///
/// Returns two byte vectors containing the key and IV respectively.
#[cfg(feature = "encryption")]
pub fn derive_key_and_iv(
&self,
cipher: Cipher,
password: impl AsRef<[u8]>,
) -> Result<(Zeroizing<Vec<u8>>, Vec<u8>)> {
let (key_size, iv_size) = cipher.key_and_iv_size().ok_or(Error::Decrypted)?;
let okm_size = key_size
.checked_add(iv_size)
.ok_or(encoding::Error::Length)?;
let mut okm = Zeroizing::new(vec![0u8; okm_size]);
self.derive(password, &mut okm)?;
let iv = okm.split_off(key_size);
Ok((okm, iv))
}
/// Is the KDF configured as `none`?
pub fn is_none(&self) -> bool {
self == &Self::None
}
/// Is the KDF configured as anything other than `none`?
pub fn is_some(&self) -> bool {
!self.is_none()
}
/// Is the KDF configured as `bcrypt` (i.e. bcrypt-pbkdf)?
#[cfg(feature = "alloc")]
pub fn is_bcrypt(&self) -> bool {
matches!(self, Self::Bcrypt { .. })
}
}
impl Default for Kdf {
fn default() -> Self {
Self::None
}
}
impl Decode for Kdf {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
match KdfAlg::decode(reader)? {
KdfAlg::None => {
if usize::decode(reader)? == 0 {
Ok(Self::None)
} else {
Err(Error::AlgorithmUnknown)
}
}
KdfAlg::Bcrypt => {
#[cfg(not(feature = "alloc"))]
return Err(Error::AlgorithmUnknown);
#[cfg(feature = "alloc")]
reader.read_prefixed(|reader| {
Ok(Self::Bcrypt {
salt: Vec::decode(reader)?,
rounds: u32::decode(reader)?,
})
})
}
}
}
}
impl Encode for Kdf {
fn encoded_len(&self) -> encoding::Result<usize> {
let kdfopts_prefixed_len = match self {
Self::None => 4,
#[cfg(feature = "alloc")]
Self::Bcrypt { salt, .. } => [12, salt.len()].checked_sum()?,
};
[self.algorithm().encoded_len()?, kdfopts_prefixed_len].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.algorithm().encode(writer)?;
match self {
Self::None => 0usize.encode(writer)?,
#[cfg(feature = "alloc")]
Self::Bcrypt { salt, rounds } => {
[8, salt.len()].checked_sum()?.encode(writer)?;
salt.encode(writer)?;
rounds.encode(writer)?
}
}
Ok(())
}
}

369
vendor/ssh-key/src/known_hosts.rs vendored Normal file
View File

@@ -0,0 +1,369 @@
//! Parser for `KnownHostsFile`-formatted data.
use crate::{Error, PublicKey, Result};
use core::str;
use encoding::base64::{Base64, Encoding};
use {
alloc::string::{String, ToString},
alloc::vec::Vec,
core::fmt,
};
#[cfg(feature = "std")]
use std::{fs, path::Path};
/// Character that begins a comment
const COMMENT_DELIMITER: char = '#';
/// The magic string prefix of a hashed hostname
const MAGIC_HASH_PREFIX: &str = "|1|";
/// Parser for `KnownHostsFile`-formatted data, typically found in
/// `~/.ssh/known_hosts`.
///
/// For a full description of the format, see:
/// <https://man7.org/linux/man-pages/man8/sshd.8.html#SSH_KNOWN_HOSTS_FILE_FORMAT>
///
/// Each line of the file consists of a single public key tied to one or more hosts.
/// Blank lines are ignored.
///
/// Public keys consist of the following space-separated fields:
///
/// ```text
/// marker, hostnames, keytype, base64-encoded key, comment
/// ```
///
/// - The marker field is optional, but if present begins with an `@`. Known markers are `@cert-authority`
/// and `@revoked`.
/// - The hostnames is a comma-separated list of patterns (with `*` and '?' as glob-style wildcards)
/// against which hosts are matched. If it begins with a `!` it is a negation of the pattern. If the
/// pattern starts with `[` and ends with `]`, it contains a hostname pattern and a port number separated
/// by a `:`. If it begins with `|1|`, the hostname is hashed. In that case, there can only be one exact
/// hostname and it can't also be negated (ie. `!|1|x|y` is not legal and you can't hash `*.example.org`).
/// - The keytype is `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`,
/// `ssh-ed25519`, `ssh-dss` or `ssh-rsa`
/// - The comment field is not used for anything (but may be convenient for the user to identify
/// the key).
pub struct KnownHosts<'a> {
/// Lines of the file being iterated over
lines: str::Lines<'a>,
}
impl<'a> KnownHosts<'a> {
/// Create a new parser for the given input buffer.
pub fn new(input: &'a str) -> Self {
Self {
lines: input.lines(),
}
}
/// Read a [`KnownHosts`] file from the filesystem, returning an
/// [`Entry`] vector on success.
#[cfg(feature = "std")]
pub fn read_file(path: impl AsRef<Path>) -> Result<Vec<Entry>> {
// TODO(tarcieri): permissions checks
let input = fs::read_to_string(path)?;
KnownHosts::new(&input).collect()
}
/// Get the next line, trimming any comments and trailing whitespace.
///
/// Ignores empty lines.
fn next_line_trimmed(&mut self) -> Option<&'a str> {
loop {
let mut line = self.lines.next()?;
// Strip comment if present
if let Some((l, _)) = line.split_once(COMMENT_DELIMITER) {
line = l;
}
// Trim trailing whitespace
line = line.trim_end();
if !line.is_empty() {
return Some(line);
}
}
}
}
impl Iterator for KnownHosts<'_> {
type Item = Result<Entry>;
fn next(&mut self) -> Option<Result<Entry>> {
self.next_line_trimmed().map(|line| line.parse())
}
}
/// Individual entry in an `known_hosts` file containing a single public key.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Entry {
/// Marker field, if present.
marker: Option<Marker>,
/// Host patterns
host_patterns: HostPatterns,
/// Public key
public_key: PublicKey,
}
impl Entry {
/// Get the marker for this entry, if present.
pub fn marker(&self) -> Option<&Marker> {
self.marker.as_ref()
}
/// Get the host pattern enumerator for this entry
pub fn host_patterns(&self) -> &HostPatterns {
&self.host_patterns
}
/// Get public key for this entry.
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
}
impl From<Entry> for Option<Marker> {
fn from(entry: Entry) -> Option<Marker> {
entry.marker
}
}
impl From<Entry> for HostPatterns {
fn from(entry: Entry) -> HostPatterns {
entry.host_patterns
}
}
impl From<Entry> for PublicKey {
fn from(entry: Entry) -> PublicKey {
entry.public_key
}
}
impl str::FromStr for Entry {
type Err = Error;
fn from_str(line: &str) -> Result<Self> {
// Unlike authorized_keys, in known_hosts it's pretty common
// to not include a key comment, so the number of spaces is
// not a reliable indicator of the fields in the line. Instead,
// the optional marker field starts with an @, so look for that
// and act accordingly.
let (marker, line) = if line.starts_with('@') {
let (marker_str, line) = line.split_once(' ').ok_or(Error::FormatEncoding)?;
(Some(marker_str.parse()?), line)
} else {
(None, line)
};
let (hosts_str, public_key_str) = line.split_once(' ').ok_or(Error::FormatEncoding)?;
let host_patterns = hosts_str.parse()?;
let public_key = public_key_str.parse()?;
Ok(Self {
marker,
host_patterns,
public_key,
})
}
}
impl ToString for Entry {
fn to_string(&self) -> String {
let mut s = String::new();
if let Some(marker) = &self.marker {
s.push_str(marker.as_str());
s.push(' ');
}
s.push_str(&self.host_patterns.to_string());
s.push(' ');
s.push_str(&self.public_key.to_string());
s
}
}
/// Markers associated with this host key entry.
///
/// There can only be one of these per host key entry.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Marker {
/// This host entry's public key is for a certificate authority's private key
CertAuthority,
/// This host entry's public key has been revoked, and should not be allowed to connect
/// regardless of any other entry.
Revoked,
}
impl Marker {
/// Get the string form of the marker
pub fn as_str(&self) -> &str {
match self {
Self::CertAuthority => "@cert-authority",
Self::Revoked => "@revoked",
}
}
}
impl AsRef<str> for Marker {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl str::FromStr for Marker {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Ok(match s {
"@cert-authority" => Marker::CertAuthority,
"@revoked" => Marker::Revoked,
_ => return Err(Error::FormatEncoding),
})
}
}
impl fmt::Display for Marker {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
/// The host pattern(s) for this host entry.
///
/// The host patterns can either be a comma separated list of host patterns
/// (which may include glob patterns (`*` and `?`), negations (a `!` prefix),
/// or `pattern:port` pairs inside square brackets), or a single hashed
/// hostname prefixed with `|1|`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum HostPatterns {
/// A comma separated list of hostname patterns.
Patterns(Vec<String>),
/// A single hashed hostname
HashedName {
/// The salt used for the hash
salt: Vec<u8>,
/// An SHA-1 hash of the hostname along with the salt
hash: [u8; 20],
},
}
impl str::FromStr for HostPatterns {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
if let Some(s) = s.strip_prefix(MAGIC_HASH_PREFIX) {
let mut hash = [0; 20];
let (salt, hash_str) = s.split_once('|').ok_or(Error::FormatEncoding)?;
let salt = Base64::decode_vec(salt)?;
Base64::decode(hash_str, &mut hash)?;
Ok(HostPatterns::HashedName { salt, hash })
} else if !s.is_empty() {
Ok(HostPatterns::Patterns(
s.split_terminator(',').map(str::to_string).collect(),
))
} else {
Err(Error::FormatEncoding)
}
}
}
impl ToString for HostPatterns {
fn to_string(&self) -> String {
match &self {
HostPatterns::Patterns(patterns) => patterns.join(","),
HostPatterns::HashedName { salt, hash } => {
let salt = Base64::encode_string(salt);
let hash = Base64::encode_string(hash);
format!("|1|{salt}|{hash}")
}
}
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use core::str::FromStr;
use super::Entry;
use super::HostPatterns;
use super::Marker;
#[test]
fn simple_markers() {
assert_eq!(Ok(Marker::CertAuthority), "@cert-authority".parse());
assert_eq!(Ok(Marker::Revoked), "@revoked".parse());
assert!(Marker::from_str("@gibberish").is_err());
}
#[test]
fn empty_host_patterns() {
assert!(HostPatterns::from_str("").is_err());
}
// Note: The sshd man page has this completely incomprehensible 'example known_hosts entry':
// closenet,...,192.0.2.53 1024 37 159...93 closenet.example.net
// I'm not sure how this one is supposed to work or what it means.
#[test]
fn single_host_pattern() {
assert_eq!(
Ok(HostPatterns::Patterns(vec!["cvs.example.net".to_string()])),
"cvs.example.net".parse()
);
}
#[test]
fn multiple_host_patterns() {
assert_eq!(
Ok(HostPatterns::Patterns(vec![
"cvs.example.net".to_string(),
"!test.example.???".to_string(),
"[*.example.net]:999".to_string(),
])),
"cvs.example.net,!test.example.???,[*.example.net]:999".parse()
);
}
#[test]
fn single_hashed_host() {
assert_eq!(
Ok(HostPatterns::HashedName {
salt: vec![
37, 242, 147, 116, 24, 123, 172, 214, 215, 145, 80, 16, 9, 26, 120, 57, 10, 15,
126, 98
],
hash: [
81, 33, 2, 175, 116, 150, 127, 82, 84, 62, 201, 172, 228, 10, 159, 15, 148, 31,
198, 67
],
}),
"|1|JfKTdBh7rNbXkVAQCRp4OQoPfmI=|USECr3SWf1JUPsms5AqfD5QfxkM=".parse()
);
}
#[test]
fn full_line_hashed() {
let line = "@revoked |1|lcY/In3lsGnkJikLENb0DM70B/I=|Qs4e9Nr7mM6avuEv02fw2uFnwQo= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB9dG4kjRhQTtWTVzd2t27+t0DEHBPW7iOD23TUiYLio comment";
let entry = Entry::from_str(line).expect("Valid entry");
assert_eq!(entry.marker(), Some(&Marker::Revoked));
assert_eq!(
entry.host_patterns(),
&HostPatterns::HashedName {
salt: vec![
149, 198, 63, 34, 125, 229, 176, 105, 228, 38, 41, 11, 16, 214, 244, 12, 206,
244, 7, 242
],
hash: [
66, 206, 30, 244, 218, 251, 152, 206, 154, 190, 225, 47, 211, 103, 240, 218,
225, 103, 193, 10
],
}
);
// key parsing is tested elsewhere
}
}

190
vendor/ssh-key/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,190 @@
#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
)]
#![forbid(unsafe_code)]
#![warn(
clippy::arithmetic_side_effects,
clippy::mod_module_files,
clippy::panic,
clippy::panic_in_result_fn,
clippy::unwrap_used,
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications
)]
//! ## Usage
//!
//! The main types provided by this library are:
//!
//! - [`Certificate`]: OpenSSH certificates
//! - [`Fingerprint`]: public key fingerprints (i.e. hashes)
//! - [`PrivateKey`]: SSH private keys (i.e. digital signature keys)
//! - [`PublicKey`]: SSH public keys (i.e. signature verification keys)
//! - [`SshSig`]: signatures with SSH keys ala `ssh-keygen -Y sign`/`ssh-keygen -Y verify`
//!
//! ### Parsing OpenSSH Public Keys
//!
//! OpenSSH-formatted public keys have the form:
//!
//! ```text
//! <algorithm id> <base64 data> <comment>
//! ```
//!
//! #### Example
//!
#![cfg_attr(feature = "std", doc = "```")]
#![cfg_attr(not(feature = "std"), doc = "```ignore")]
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use ssh_key::PublicKey;
//!
//! let encoded_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti user@example.com";
//! let public_key = PublicKey::from_openssh(encoded_key)?;
//!
//! // Key attributes
//! assert_eq!(public_key.algorithm(), ssh_key::Algorithm::Ed25519);
//! assert_eq!(public_key.comment(), "user@example.com");
//!
//! // Key data: in this example an Ed25519 key
//! if let Some(ed25519_public_key) = public_key.key_data().ed25519() {
//! assert_eq!(
//! ed25519_public_key.as_ref(),
//! [
//! 0xb3, 0x3e, 0xae, 0xf3, 0x7e, 0xa2, 0xdf, 0x7c, 0xaa, 0x1, 0xd, 0xef, 0xde, 0xa3,
//! 0x4e, 0x24, 0x1f, 0x65, 0xf1, 0xb5, 0x29, 0xa4, 0xf4, 0x3e, 0xd1, 0x43, 0x27, 0xf5,
//! 0xc5, 0x4a, 0xab, 0x62
//! ].as_ref()
//! );
//! }
//! # Ok(())
//! # }
//! ```
//!
//! ### Parsing OpenSSH Private Keys
//!
//! *NOTE: for more private key usage examples, see the [`private`] module.*
//!
//! OpenSSH-formatted private keys are PEM-encoded and begin with the following:
//!
//! ```text
//! -----BEGIN OPENSSH PRIVATE KEY-----
//! ```
//!
//! #### Example
//!
#![cfg_attr(feature = "std", doc = " ```")]
#![cfg_attr(not(feature = "std"), doc = " ```ignore")]
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use ssh_key::PrivateKey;
//!
//! // WARNING: don't actually hardcode private keys in source code!!!
//! let encoded_key = r#"
//! -----BEGIN OPENSSH PRIVATE KEY-----
//! b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
//! QyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYgAAAJgAIAxdACAM
//! XQAAAAtzc2gtZWQyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYg
//! AAAEC2BsIi0QwW2uFscKTUUXNHLsYX4FxlaSDSblbAj7WR7bM+rvN+ot98qgEN796jTiQf
//! ZfG1KaT0PtFDJ/XFSqtiAAAAEHVzZXJAZXhhbXBsZS5jb20BAgMEBQ==
//! -----END OPENSSH PRIVATE KEY-----
//! "#;
//!
//! let private_key = PrivateKey::from_openssh(encoded_key)?;
//!
//! // Key attributes
//! assert_eq!(private_key.algorithm(), ssh_key::Algorithm::Ed25519);
//! assert_eq!(private_key.comment(), "user@example.com");
//!
//! // Key data: in this example an Ed25519 key
//! if let Some(ed25519_keypair) = private_key.key_data().ed25519() {
//! assert_eq!(
//! ed25519_keypair.public.as_ref(),
//! [
//! 0xb3, 0x3e, 0xae, 0xf3, 0x7e, 0xa2, 0xdf, 0x7c, 0xaa, 0x1, 0xd, 0xef, 0xde, 0xa3,
//! 0x4e, 0x24, 0x1f, 0x65, 0xf1, 0xb5, 0x29, 0xa4, 0xf4, 0x3e, 0xd1, 0x43, 0x27, 0xf5,
//! 0xc5, 0x4a, 0xab, 0x62
//! ].as_ref()
//! );
//!
//! assert_eq!(
//! ed25519_keypair.private.as_ref(),
//! [
//! 0xb6, 0x6, 0xc2, 0x22, 0xd1, 0xc, 0x16, 0xda, 0xe1, 0x6c, 0x70, 0xa4, 0xd4, 0x51,
//! 0x73, 0x47, 0x2e, 0xc6, 0x17, 0xe0, 0x5c, 0x65, 0x69, 0x20, 0xd2, 0x6e, 0x56, 0xc0,
//! 0x8f, 0xb5, 0x91, 0xed
//! ].as_ref()
//! )
//! }
//! # Ok(())
//! # }
//! ```
//!
//! ## `serde` support
//!
//! When the `serde` feature of this crate is enabled, the [`Certificate`],
//! [`Fingerprint`], and [`PublicKey`] types receive impls of `serde`'s
//! [`Deserialize`][`serde::Deserialize`] and [`Serialize`][`serde::Serialize`]
//! traits.
//!
//! Serializing/deserializing [`PrivateKey`] using `serde` is presently
//! unsupported.
#[cfg(feature = "alloc")]
#[macro_use]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
pub mod authorized_keys;
pub mod private;
pub mod public;
#[cfg(feature = "alloc")]
pub mod certificate;
#[cfg(feature = "alloc")]
pub mod known_hosts;
mod algorithm;
mod error;
mod fingerprint;
mod kdf;
#[cfg(feature = "alloc")]
mod mpint;
#[cfg(feature = "alloc")]
mod signature;
#[cfg(feature = "alloc")]
mod sshsig;
pub use crate::{
algorithm::{Algorithm, EcdsaCurve, HashAlg, KdfAlg},
authorized_keys::AuthorizedKeys,
error::{Error, Result},
fingerprint::Fingerprint,
kdf::Kdf,
private::PrivateKey,
public::PublicKey,
};
pub use cipher::Cipher;
pub use encoding::LineEnding;
pub use sha2;
#[cfg(feature = "alloc")]
pub use crate::{
algorithm::AlgorithmName,
certificate::Certificate,
known_hosts::KnownHosts,
mpint::Mpint,
signature::{Signature, SigningKey},
sshsig::SshSig,
};
#[cfg(feature = "ecdsa")]
pub use sec1;
#[cfg(feature = "rand_core")]
pub use rand_core;

297
vendor/ssh-key/src/mpint.rs vendored Normal file
View File

@@ -0,0 +1,297 @@
//! Multiple precision integer
use crate::{Error, Result};
use alloc::{boxed::Box, vec::Vec};
use core::fmt;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use subtle::{Choice, ConstantTimeEq};
use zeroize::Zeroize;
#[cfg(any(feature = "dsa", feature = "rsa"))]
use zeroize::Zeroizing;
/// Multiple precision integer, a.k.a. "mpint".
///
/// This type is used for representing the big integer components of
/// DSA and RSA keys.
///
/// Described in [RFC4251 § 5](https://datatracker.ietf.org/doc/html/rfc4251#section-5):
///
/// > Represents multiple precision integers in two's complement format,
/// > stored as a string, 8 bits per byte, MSB first. Negative numbers
/// > have the value 1 as the most significant bit of the first byte of
/// > the data partition. If the most significant bit would be set for
/// > a positive number, the number MUST be preceded by a zero byte.
/// > Unnecessary leading bytes with the value 0 or 255 MUST NOT be
/// > included. The value zero MUST be stored as a string with zero
/// > bytes of data.
/// >
/// > By convention, a number that is used in modular computations in
/// > Z_n SHOULD be represented in the range 0 <= x < n.
///
/// ## Examples
///
/// | value (hex) | representation (hex) |
/// |-----------------|----------------------|
/// | 0 | `00 00 00 00`
/// | 9a378f9b2e332a7 | `00 00 00 08 09 a3 78 f9 b2 e3 32 a7`
/// | 80 | `00 00 00 02 00 80`
/// |-1234 | `00 00 00 02 ed cc`
/// | -deadbeef | `00 00 00 05 ff 21 52 41 11`
#[derive(Clone, PartialOrd, Ord)]
pub struct Mpint {
/// Inner big endian-serialized integer value
inner: Box<[u8]>,
}
impl Mpint {
/// Create a new multiple precision integer from the given
/// big endian-encoded byte slice.
///
/// Note that this method expects a leading zero on positive integers whose
/// MSB is set, but does *NOT* expect a 4-byte length prefix.
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
bytes.try_into()
}
/// Create a new multiple precision integer from the given big endian
/// encoded byte slice representing a positive integer.
///
/// The input may begin with leading zeros, which will be stripped when
/// converted to [`Mpint`] encoding.
pub fn from_positive_bytes(mut bytes: &[u8]) -> Result<Self> {
let mut inner = Vec::with_capacity(bytes.len());
while bytes.first().copied() == Some(0) {
bytes = &bytes[1..];
}
match bytes.first().copied() {
Some(n) if n >= 0x80 => inner.push(0),
_ => (),
}
inner.extend_from_slice(bytes);
inner.into_boxed_slice().try_into()
}
/// Get the big integer data encoded as big endian bytes.
///
/// This slice will contain a leading zero if the value is positive but the
/// MSB is also set. Use [`Mpint::as_positive_bytes`] to ensure the number
/// is positive and strip the leading zero byte if it exists.
pub fn as_bytes(&self) -> &[u8] {
&self.inner
}
/// Get the bytes of a positive integer.
///
/// # Returns
/// - `Some(bytes)` if the number is positive. The leading zero byte will be stripped.
/// - `None` if the value is negative
pub fn as_positive_bytes(&self) -> Option<&[u8]> {
match self.as_bytes() {
[0x00, rest @ ..] => Some(rest),
[byte, ..] if *byte < 0x80 => Some(self.as_bytes()),
_ => None,
}
}
}
impl AsRef<[u8]> for Mpint {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl ConstantTimeEq for Mpint {
fn ct_eq(&self, other: &Self) -> Choice {
self.as_ref().ct_eq(other.as_ref())
}
}
impl Eq for Mpint {}
impl PartialEq for Mpint {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Decode for Mpint {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
Vec::decode(reader)?.into_boxed_slice().try_into()
}
}
impl Encode for Mpint {
fn encoded_len(&self) -> encoding::Result<usize> {
[4, self.as_bytes().len()].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.as_bytes().encode(writer)?;
Ok(())
}
}
impl TryFrom<&[u8]> for Mpint {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Self> {
Vec::from(bytes).into_boxed_slice().try_into()
}
}
impl TryFrom<Box<[u8]>> for Mpint {
type Error = Error;
fn try_from(bytes: Box<[u8]>) -> Result<Self> {
match &*bytes {
// Unnecessary leading 0
[0x00] => Err(Error::FormatEncoding),
// Unnecessary leading 0
[0x00, n, ..] if *n < 0x80 => Err(Error::FormatEncoding),
_ => Ok(Self { inner: bytes }),
}
}
}
impl Zeroize for Mpint {
fn zeroize(&mut self) {
self.inner.zeroize();
}
}
impl fmt::Debug for Mpint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Mpint({self:X})")
}
}
impl fmt::Display for Mpint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:X}")
}
}
impl fmt::LowerHex for Mpint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_bytes() {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
impl fmt::UpperHex for Mpint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_bytes() {
write!(f, "{byte:02X}")?;
}
Ok(())
}
}
#[cfg(any(feature = "dsa", feature = "rsa"))]
impl TryFrom<bigint::BigUint> for Mpint {
type Error = Error;
fn try_from(uint: bigint::BigUint) -> Result<Mpint> {
Mpint::try_from(&uint)
}
}
#[cfg(any(feature = "dsa", feature = "rsa"))]
impl TryFrom<&bigint::BigUint> for Mpint {
type Error = Error;
fn try_from(uint: &bigint::BigUint) -> Result<Mpint> {
let bytes = Zeroizing::new(uint.to_bytes_be());
Mpint::from_positive_bytes(bytes.as_slice())
}
}
#[cfg(any(feature = "dsa", feature = "rsa"))]
impl TryFrom<Mpint> for bigint::BigUint {
type Error = Error;
fn try_from(mpint: Mpint) -> Result<bigint::BigUint> {
bigint::BigUint::try_from(&mpint)
}
}
#[cfg(any(feature = "dsa", feature = "rsa"))]
impl TryFrom<&Mpint> for bigint::BigUint {
type Error = Error;
fn try_from(mpint: &Mpint) -> Result<bigint::BigUint> {
mpint
.as_positive_bytes()
.map(bigint::BigUint::from_bytes_be)
.ok_or(Error::Crypto)
}
}
#[cfg(test)]
mod tests {
use super::Mpint;
use hex_literal::hex;
#[test]
fn decode_0() {
let n = Mpint::from_bytes(b"").unwrap();
assert_eq!(b"", n.as_bytes())
}
#[test]
fn reject_extra_leading_zeroes() {
assert!(Mpint::from_bytes(&hex!("00")).is_err());
assert!(Mpint::from_bytes(&hex!("00 00")).is_err());
assert!(Mpint::from_bytes(&hex!("00 01")).is_err());
}
#[test]
fn decode_9a378f9b2e332a7() {
assert!(Mpint::from_bytes(&hex!("09 a3 78 f9 b2 e3 32 a7")).is_ok());
}
#[test]
fn decode_80() {
let n = Mpint::from_bytes(&hex!("00 80")).unwrap();
// Leading zero stripped
assert_eq!(&hex!("80"), n.as_positive_bytes().unwrap())
}
#[test]
fn from_positive_bytes_strips_leading_zeroes() {
assert_eq!(
Mpint::from_positive_bytes(&hex!("00")).unwrap().as_ref(),
b""
);
assert_eq!(
Mpint::from_positive_bytes(&hex!("00 00")).unwrap().as_ref(),
b""
);
assert_eq!(
Mpint::from_positive_bytes(&hex!("00 01")).unwrap().as_ref(),
b"\x01"
);
}
// TODO(tarcieri): drop support for negative numbers?
#[test]
fn decode_neg_1234() {
let n = Mpint::from_bytes(&hex!("ed cc")).unwrap();
assert!(n.as_positive_bytes().is_none());
}
// TODO(tarcieri): drop support for negative numbers?
#[test]
fn decode_neg_deadbeef() {
let n = Mpint::from_bytes(&hex!("ff 21 52 41 11")).unwrap();
assert!(n.as_positive_bytes().is_none());
}
}

895
vendor/ssh-key/src/private.rs vendored Normal file
View File

@@ -0,0 +1,895 @@
//! SSH private key support.
//!
//! Support for decoding SSH private keys (i.e. digital signature keys)
//! from the OpenSSH file format:
//!
//! <https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD>
//!
//! ## Decrypting encrypted private keys
//!
//! When the `encryption` feature of this crate is enabled, it's possible to
//! decrypt keys which have been encrypted under a password:
//!
#![cfg_attr(all(feature = "encryption", feature = "std"), doc = " ```")]
#![cfg_attr(not(all(feature = "encryption", feature = "std")), doc = " ```ignore")]
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use ssh_key::PrivateKey;
//!
//! // WARNING: don't actually hardcode private keys in source code!!!
//! let encoded_key = r#"
//! -----BEGIN OPENSSH PRIVATE KEY-----
//! b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBKH96ujW
//! umB6/WnTNPjTeaAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN
//! 796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAoFzvbvyFMhAiwBOXF0mhUUacPUCMZXivG2up2c
//! hEnAw1b6BLRPyWbY5cC2n9ggD4ivJ1zSts6sBgjyiXQAReyrP35myYvT/OIB/NpwZM/xIJ
//! N7MHSUzlkX4adBrga3f7GS4uv4ChOoxC4XsE5HsxtGsq1X8jzqLlZTmOcxkcEneYQexrUc
//! bQP0o+gL5aKK8cQgiIlXeDbRjqhc4+h4EF6lY=
//! -----END OPENSSH PRIVATE KEY-----
//! "#;
//!
//! let encrypted_key = PrivateKey::from_openssh(encoded_key)?;
//! assert!(encrypted_key.is_encrypted());
//!
//! // WARNING: don't hardcode passwords, and this one's bad anyway
//! let password = "hunter42";
//!
//! let decrypted_key = encrypted_key.decrypt(password)?;
//! assert!(!decrypted_key.is_encrypted());
//! # Ok(())
//! # }
//! ```
//!
//! ## Encrypting plaintext private keys
//!
//! When the `encryption` feature of this crate is enabled, it's possible to
//! encrypt plaintext private keys under a provided password.
//!
//! The example below also requires enabling this crate's `getrandom` feature.
//!
#![cfg_attr(
all(
feature = "ed25519",
feature = "encryption",
feature = "getrandom",
feature = "std"
),
doc = " ```"
)]
#![cfg_attr(
not(all(
feature = "ed25519",
feature = "encryption",
feature = "getrandom",
feature = "std"
)),
doc = " ```ignore"
)]
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use ssh_key::{Algorithm, PrivateKey, rand_core::OsRng};
//!
//! // Generate a random key
//! let unencrypted_key = PrivateKey::random(&mut OsRng, Algorithm::Ed25519)?;
//!
//! // WARNING: don't hardcode passwords, and this one's bad anyway
//! let password = "hunter42";
//!
//! let encrypted_key = unencrypted_key.encrypt(&mut OsRng, password)?;
//! assert!(encrypted_key.is_encrypted());
//! # Ok(())
//! # }
//! ```
//!
//! ## Generating random keys
//!
//! This crate supports generation of random keys using algorithm-specific
//! backends gated on cargo features.
//!
//! The examples below require enabling this crate's `getrandom` feature as
//! well as the crate feature identified in backticks in the title of each
//! example.
//!
#![cfg_attr(
all(feature = "ed25519", feature = "getrandom", feature = "std"),
doc = " ```"
)]
#![cfg_attr(
not(all(feature = "ed25519", feature = "getrandom", feature = "std")),
doc = " ```ignore"
)]
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use ssh_key::{Algorithm, PrivateKey, rand_core::OsRng};
//!
//! let private_key = PrivateKey::random(&mut OsRng, Algorithm::Ed25519)?;
//! # Ok(())
//! # }
//! ```
#[cfg(feature = "alloc")]
mod dsa;
#[cfg(feature = "ecdsa")]
mod ecdsa;
mod ed25519;
mod keypair;
#[cfg(feature = "alloc")]
mod opaque;
#[cfg(feature = "alloc")]
mod rsa;
#[cfg(feature = "alloc")]
mod sk;
pub use self::{
ed25519::{Ed25519Keypair, Ed25519PrivateKey},
keypair::KeypairData,
};
#[cfg(feature = "alloc")]
pub use crate::{
private::{
dsa::{DsaKeypair, DsaPrivateKey},
opaque::{OpaqueKeypair, OpaqueKeypairBytes, OpaquePrivateKeyBytes},
rsa::{RsaKeypair, RsaPrivateKey},
sk::SkEd25519,
},
SshSig,
};
#[cfg(feature = "ecdsa")]
pub use self::ecdsa::{EcdsaKeypair, EcdsaPrivateKey};
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
pub use self::sk::SkEcdsaSha2NistP256;
use crate::{public, Algorithm, Cipher, Error, Fingerprint, HashAlg, Kdf, PublicKey, Result};
use cipher::Tag;
use core::str;
use encoding::{
pem::{LineEnding, PemLabel},
CheckedSum, Decode, DecodePem, Encode, EncodePem, Reader, Writer,
};
use subtle::{Choice, ConstantTimeEq};
#[cfg(feature = "alloc")]
use {
alloc::{string::String, vec::Vec},
zeroize::Zeroizing,
};
#[cfg(feature = "rand_core")]
use rand_core::CryptoRngCore;
#[cfg(feature = "std")]
use std::{fs, path::Path};
#[cfg(all(unix, feature = "std"))]
use std::{io::Write, os::unix::fs::OpenOptionsExt};
/// Error message for infallible conversions (used by `expect`)
const CONVERSION_ERROR_MSG: &str = "SSH private key conversion error";
/// Default key size to use for RSA keys in bits.
#[cfg(all(feature = "rand_core", feature = "rsa"))]
const DEFAULT_RSA_KEY_SIZE: usize = 4096;
/// Maximum supported block size.
///
/// This is the block size used by e.g. AES.
const MAX_BLOCK_SIZE: usize = 16;
/// Padding bytes to use.
const PADDING_BYTES: [u8; MAX_BLOCK_SIZE - 1] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
/// Unix file permissions for SSH private keys.
#[cfg(all(unix, feature = "std"))]
const UNIX_FILE_PERMISSIONS: u32 = 0o600;
/// SSH private key.
#[derive(Clone, Debug)]
pub struct PrivateKey {
/// Cipher algorithm.
cipher: Cipher,
/// KDF options.
kdf: Kdf,
/// "Checkint" value used to verify successful decryption.
checkint: Option<u32>,
/// Public key.
public_key: PublicKey,
/// Private keypair data.
key_data: KeypairData,
/// Authentication tag for authenticated encryption modes.
auth_tag: Option<Tag>,
}
impl PrivateKey {
/// Magic string used to identify keys in this format.
const AUTH_MAGIC: &'static [u8] = b"openssh-key-v1\0";
/// Create a new unencrypted private key with the given keypair data and comment.
///
/// On `no_std` platforms, use `PrivateKey::from(key_data)` instead.
#[cfg(feature = "alloc")]
pub fn new(key_data: KeypairData, comment: impl Into<String>) -> Result<Self> {
if key_data.is_encrypted() {
return Err(Error::Encrypted);
}
let mut private_key = Self::try_from(key_data)?;
private_key.public_key.comment = comment.into();
Ok(private_key)
}
/// Parse an OpenSSH-formatted PEM private key.
///
/// OpenSSH-formatted private keys begin with the following:
///
/// ```text
/// -----BEGIN OPENSSH PRIVATE KEY-----
/// ```
pub fn from_openssh(pem: impl AsRef<[u8]>) -> Result<Self> {
Self::decode_pem(pem)
}
/// Parse a raw binary SSH private key.
pub fn from_bytes(mut bytes: &[u8]) -> Result<Self> {
let reader = &mut bytes;
let private_key = Self::decode(reader)?;
Ok(reader.finish(private_key)?)
}
/// Encode OpenSSH-formatted (PEM) private key.
pub fn encode_openssh<'o>(
&self,
line_ending: LineEnding,
out: &'o mut [u8],
) -> Result<&'o str> {
Ok(self.encode_pem(line_ending, out)?)
}
/// Encode an OpenSSH-formatted PEM private key, allocating a
/// self-zeroizing [`String`] for the result.
#[cfg(feature = "alloc")]
pub fn to_openssh(&self, line_ending: LineEnding) -> Result<Zeroizing<String>> {
Ok(self.encode_pem_string(line_ending).map(Zeroizing::new)?)
}
/// Serialize SSH private key as raw bytes.
#[cfg(feature = "alloc")]
pub fn to_bytes(&self) -> Result<Zeroizing<Vec<u8>>> {
let mut private_key_bytes = Vec::with_capacity(self.encoded_len()?);
self.encode(&mut private_key_bytes)?;
Ok(Zeroizing::new(private_key_bytes))
}
/// Sign the given message using this private key, returning an [`SshSig`].
///
/// These signatures can be produced using `ssh-keygen -Y sign`. They're
/// encoded as PEM and begin with the following:
///
/// ```text
/// -----BEGIN SSH SIGNATURE-----
/// ```
///
/// See [PROTOCOL.sshsig] for more information.
///
/// # Usage
///
/// See also: [`PublicKey::verify`].
///
#[cfg_attr(feature = "ed25519", doc = "```")]
#[cfg_attr(not(feature = "ed25519"), doc = "```ignore")]
/// # fn main() -> Result<(), ssh_key::Error> {
/// use ssh_key::{PrivateKey, HashAlg, SshSig};
///
/// // Message to be signed.
/// let message = b"testing";
///
/// // Example domain/namespace used for the message.
/// let namespace = "example";
///
/// // Private key to use when computing the signature.
/// // WARNING: don't actually hardcode private keys in source code!!!
/// let encoded_private_key = r#"
/// -----BEGIN OPENSSH PRIVATE KEY-----
/// b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
/// QyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYgAAAJgAIAxdACAM
/// XQAAAAtzc2gtZWQyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYg
/// AAAEC2BsIi0QwW2uFscKTUUXNHLsYX4FxlaSDSblbAj7WR7bM+rvN+ot98qgEN796jTiQf
/// ZfG1KaT0PtFDJ/XFSqtiAAAAEHVzZXJAZXhhbXBsZS5jb20BAgMEBQ==
/// -----END OPENSSH PRIVATE KEY-----
/// "#;
///
/// let private_key = encoded_private_key.parse::<PrivateKey>()?;
/// let signature = private_key.sign(namespace, HashAlg::default(), message)?;
/// // assert!(private_key.public_key().verify(namespace, message, &signature).is_ok());
/// # Ok(())
/// # }
/// ```
///
/// [PROTOCOL.sshsig]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.sshsig?annotate=HEAD
#[cfg(feature = "alloc")]
pub fn sign(&self, namespace: &str, hash_alg: HashAlg, msg: &[u8]) -> Result<SshSig> {
SshSig::sign(self, namespace, hash_alg, msg)
}
/// Read private key from an OpenSSH-formatted PEM file.
#[cfg(feature = "std")]
pub fn read_openssh_file(path: &Path) -> Result<Self> {
// TODO(tarcieri): verify file permissions match `UNIX_FILE_PERMISSIONS`
let pem = Zeroizing::new(fs::read_to_string(path)?);
Self::from_openssh(&*pem)
}
/// Write private key as an OpenSSH-formatted PEM file.
#[cfg(feature = "std")]
pub fn write_openssh_file(&self, path: &Path, line_ending: LineEnding) -> Result<()> {
let pem = self.to_openssh(line_ending)?;
#[cfg(not(unix))]
fs::write(path, pem.as_bytes())?;
#[cfg(unix)]
fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.mode(UNIX_FILE_PERMISSIONS)
.open(path)
.and_then(|mut file| file.write_all(pem.as_bytes()))?;
Ok(())
}
/// Attempt to decrypt an encrypted private key using the provided
/// password to derive an encryption key.
///
/// Returns [`Error::Decrypted`] if the private key is already decrypted.
#[cfg(feature = "encryption")]
pub fn decrypt(&self, password: impl AsRef<[u8]>) -> Result<Self> {
let (key, iv) = self.kdf.derive_key_and_iv(self.cipher, password)?;
let ciphertext = self.key_data.encrypted().ok_or(Error::Decrypted)?;
let mut buffer = Zeroizing::new(ciphertext.to_vec());
self.cipher.decrypt(&key, &iv, &mut buffer, self.auth_tag)?;
Self::decode_privatekey_comment_pair(
&mut &**buffer,
self.public_key.key_data.clone(),
self.cipher.block_size(),
)
}
/// Encrypt an unencrypted private key using the provided password to
/// derive an encryption key.
///
/// Uses the following algorithms:
/// - Cipher: [`Cipher::Aes256Ctr`]
/// - KDF: [`Kdf::Bcrypt`] (i.e. `bcrypt-pbkdf`)
///
/// Returns [`Error::Encrypted`] if the private key is already encrypted.
#[cfg(feature = "encryption")]
pub fn encrypt(
&self,
rng: &mut impl CryptoRngCore,
password: impl AsRef<[u8]>,
) -> Result<Self> {
self.encrypt_with_cipher(rng, Cipher::Aes256Ctr, password)
}
/// Encrypt an unencrypted private key using the provided password to
/// derive an encryption key for the provided [`Cipher`].
///
/// Returns [`Error::Encrypted`] if the private key is already encrypted.
#[cfg(feature = "encryption")]
pub fn encrypt_with_cipher(
&self,
rng: &mut impl CryptoRngCore,
cipher: Cipher,
password: impl AsRef<[u8]>,
) -> Result<Self> {
let checkint = rng.next_u32();
self.encrypt_with(
cipher,
Kdf::new(Default::default(), rng)?,
checkint,
password,
)
}
/// Encrypt an unencrypted private key using the provided cipher and KDF
/// configuration.
///
/// Returns [`Error::Encrypted`] if the private key is already encrypted.
#[cfg(feature = "encryption")]
pub fn encrypt_with(
&self,
cipher: Cipher,
kdf: Kdf,
checkint: u32,
password: impl AsRef<[u8]>,
) -> Result<Self> {
if self.is_encrypted() {
return Err(Error::Encrypted);
}
let (key_bytes, iv_bytes) = kdf.derive_key_and_iv(cipher, password)?;
let msg_len = self.encoded_privatekey_comment_pair_len(cipher)?;
let mut out = Vec::with_capacity(msg_len);
// Encode and encrypt private key
self.encode_privatekey_comment_pair(&mut out, cipher, checkint)?;
let auth_tag = cipher.encrypt(&key_bytes, &iv_bytes, out.as_mut_slice())?;
Ok(Self {
cipher,
kdf,
checkint: None,
public_key: self.public_key.key_data.clone().into(),
key_data: KeypairData::Encrypted(out),
auth_tag,
})
}
/// Get the digital signature [`Algorithm`] used by this key.
pub fn algorithm(&self) -> Algorithm {
self.public_key.algorithm()
}
/// Comment on the key (e.g. email address).
pub fn comment(&self) -> &str {
self.public_key.comment()
}
/// Cipher algorithm (a.k.a. `ciphername`).
pub fn cipher(&self) -> Cipher {
self.cipher
}
/// Compute key fingerprint.
///
/// Use [`Default::default()`] to use the default hash function (SHA-256).
pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint {
self.public_key.fingerprint(hash_alg)
}
/// Is this key encrypted?
pub fn is_encrypted(&self) -> bool {
let ret = self.key_data.is_encrypted();
debug_assert_eq!(ret, self.cipher.is_some());
ret
}
/// Key Derivation Function (KDF) used to encrypt this key.
///
/// Returns [`Kdf::None`] if this key is not encrypted.
pub fn kdf(&self) -> &Kdf {
&self.kdf
}
/// Keypair data.
pub fn key_data(&self) -> &KeypairData {
&self.key_data
}
/// Get the [`PublicKey`] which corresponds to this private key.
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
/// Generate a random key which uses the given algorithm.
///
/// # Returns
/// - `Error::AlgorithmUnknown` if the algorithm is unsupported.
#[cfg(feature = "rand_core")]
#[allow(unreachable_code, unused_variables)]
pub fn random(rng: &mut impl CryptoRngCore, algorithm: Algorithm) -> Result<Self> {
let checkint = rng.next_u32();
let key_data = match algorithm {
#[cfg(feature = "dsa")]
Algorithm::Dsa => KeypairData::from(DsaKeypair::random(rng)?),
#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
Algorithm::Ecdsa { curve } => KeypairData::from(EcdsaKeypair::random(rng, curve)?),
#[cfg(feature = "ed25519")]
Algorithm::Ed25519 => KeypairData::from(Ed25519Keypair::random(rng)),
#[cfg(feature = "rsa")]
Algorithm::Rsa { .. } => {
KeypairData::from(RsaKeypair::random(rng, DEFAULT_RSA_KEY_SIZE)?)
}
_ => return Err(Error::AlgorithmUnknown),
};
let public_key = public::KeyData::try_from(&key_data)?;
Ok(Self {
cipher: Cipher::None,
kdf: Kdf::None,
checkint: Some(checkint),
public_key: public_key.into(),
key_data,
auth_tag: None,
})
}
/// Set the comment on the key.
#[cfg(feature = "alloc")]
pub fn set_comment(&mut self, comment: impl Into<String>) {
self.public_key.set_comment(comment);
}
/// Decode [`KeypairData`] along with its associated checkints and comment,
/// storing the comment in the provided public key on success.
///
/// This method also checks padding for validity and ensures that the
/// decoded private key matches the provided public key.
///
/// For private key format specification, see OpenSSH [PROTOCOL.key] § 3:
///
/// ```text
/// uint32 checkint
/// uint32 checkint
/// byte[] privatekey1
/// string comment1
/// byte[] privatekey2
/// string comment2
/// ...
/// string privatekeyN
/// string commentN
/// char 1
/// char 2
/// char 3
/// ...
/// char padlen % 255
/// ```
///
/// [PROTOCOL.key]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD
fn decode_privatekey_comment_pair(
reader: &mut impl Reader,
public_key: public::KeyData,
block_size: usize,
) -> Result<Self> {
debug_assert!(block_size <= MAX_BLOCK_SIZE);
// Ensure input data is padding-aligned
if reader.remaining_len().checked_rem(block_size) != Some(0) {
return Err(encoding::Error::Length.into());
}
let checkint1 = u32::decode(reader)?;
let checkint2 = u32::decode(reader)?;
if checkint1 != checkint2 {
return Err(Error::Crypto);
}
let key_data = KeypairData::decode(reader)?;
// Ensure public key matches private key
if public_key != public::KeyData::try_from(&key_data)? {
return Err(Error::PublicKey);
}
let mut public_key = PublicKey::from(public_key);
public_key.decode_comment(reader)?;
let padding_len = reader.remaining_len();
if padding_len >= block_size {
return Err(encoding::Error::Length.into());
}
if padding_len != 0 {
let mut padding = [0u8; MAX_BLOCK_SIZE];
reader.read(&mut padding[..padding_len])?;
if PADDING_BYTES[..padding_len] != padding[..padding_len] {
return Err(Error::FormatEncoding);
}
}
if !reader.is_finished() {
return Err(Error::TrailingData {
remaining: reader.remaining_len(),
});
}
Ok(Self {
cipher: Cipher::None,
kdf: Kdf::None,
checkint: Some(checkint1),
public_key,
key_data,
auth_tag: None,
})
}
/// Encode [`KeypairData`] along with its associated checkints, comment,
/// and padding.
fn encode_privatekey_comment_pair(
&self,
writer: &mut impl Writer,
cipher: Cipher,
checkint: u32,
) -> encoding::Result<()> {
let unpadded_len = self.unpadded_privatekey_comment_pair_len()?;
let padding_len = cipher.padding_len(unpadded_len);
checkint.encode(writer)?;
checkint.encode(writer)?;
self.key_data.encode(writer)?;
self.comment().encode(writer)?;
writer.write(&PADDING_BYTES[..padding_len])?;
Ok(())
}
/// Get the length of this private key when encoded with the given comment
/// and padded using the padding size for the given cipher.
fn encoded_privatekey_comment_pair_len(&self, cipher: Cipher) -> encoding::Result<usize> {
let len = self.unpadded_privatekey_comment_pair_len()?;
[len, cipher.padding_len(len)].checked_sum()
}
/// Get the length of this private key when encoded with the given comment.
///
/// This length is just the checkints, private key data, and comment sans
/// any padding.
fn unpadded_privatekey_comment_pair_len(&self) -> encoding::Result<usize> {
// This method is intended for use with unencrypted keys only
debug_assert!(!self.is_encrypted(), "called on encrypted key");
[
8, // 2 x uint32 checkints,
self.key_data.encoded_len()?,
self.comment().encoded_len()?,
]
.checked_sum()
}
}
impl ConstantTimeEq for PrivateKey {
fn ct_eq(&self, other: &Self) -> Choice {
// Constant-time with respect to private key data
self.key_data.ct_eq(&other.key_data)
& Choice::from(
(self.cipher == other.cipher
&& self.kdf == other.kdf
&& self.public_key == other.public_key) as u8,
)
}
}
impl Eq for PrivateKey {}
impl PartialEq for PrivateKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Decode for PrivateKey {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let mut auth_magic = [0u8; Self::AUTH_MAGIC.len()];
reader.read(&mut auth_magic)?;
if auth_magic != Self::AUTH_MAGIC {
return Err(Error::FormatEncoding);
}
let cipher = Cipher::decode(reader)?;
let kdf = Kdf::decode(reader)?;
let nkeys = usize::decode(reader)?;
// TODO(tarcieri): support more than one key?
if nkeys != 1 {
return Err(encoding::Error::Length.into());
}
let public_key = reader.read_prefixed(public::KeyData::decode)?;
// Handle encrypted private key
#[cfg(not(feature = "alloc"))]
if cipher.is_some() {
return Err(Error::Encrypted);
}
#[cfg(feature = "alloc")]
if cipher.is_some() {
let ciphertext = Vec::decode(reader)?;
// Ensure ciphertext is padded to the expected length
if ciphertext.len().checked_rem(cipher.block_size()) != Some(0) {
return Err(Error::Crypto);
}
let auth_tag = if cipher.has_tag() {
let mut tag = Tag::default();
reader.read(&mut tag)?;
Some(tag)
} else {
None
};
if !reader.is_finished() {
return Err(Error::TrailingData {
remaining: reader.remaining_len(),
});
}
return Ok(Self {
cipher,
kdf,
checkint: None,
public_key: public_key.into(),
key_data: KeypairData::Encrypted(ciphertext),
auth_tag,
});
}
// Processing unencrypted key. No KDF should be set.
if kdf.is_some() {
return Err(Error::Crypto);
}
reader.read_prefixed(|reader| {
Self::decode_privatekey_comment_pair(reader, public_key, cipher.block_size())
})
}
}
impl Encode for PrivateKey {
fn encoded_len(&self) -> encoding::Result<usize> {
let private_key_len = if self.is_encrypted() {
self.key_data.encoded_len_prefixed()?
} else {
[4, self.encoded_privatekey_comment_pair_len(Cipher::None)?].checked_sum()?
};
[
Self::AUTH_MAGIC.len(),
self.cipher.encoded_len()?,
self.kdf.encoded_len()?,
4, // number of keys (uint32)
self.public_key.key_data().encoded_len_prefixed()?,
private_key_len,
self.auth_tag.map(|tag| tag.len()).unwrap_or(0),
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
writer.write(Self::AUTH_MAGIC)?;
self.cipher.encode(writer)?;
self.kdf.encode(writer)?;
// TODO(tarcieri): support for encoding more than one private key
1usize.encode(writer)?;
// Encode public key
self.public_key.key_data().encode_prefixed(writer)?;
// Encode private key
if self.is_encrypted() {
self.key_data.encode_prefixed(writer)?;
if let Some(tag) = &self.auth_tag {
writer.write(tag)?;
}
} else {
self.encoded_privatekey_comment_pair_len(Cipher::None)?
.encode(writer)?;
let checkint = self.checkint.unwrap_or_else(|| self.key_data.checkint());
self.encode_privatekey_comment_pair(writer, Cipher::None, checkint)?;
}
Ok(())
}
}
impl From<PrivateKey> for PublicKey {
fn from(private_key: PrivateKey) -> PublicKey {
private_key.public_key
}
}
impl From<&PrivateKey> for PublicKey {
fn from(private_key: &PrivateKey) -> PublicKey {
private_key.public_key.clone()
}
}
impl From<PrivateKey> for public::KeyData {
fn from(private_key: PrivateKey) -> public::KeyData {
private_key.public_key.key_data
}
}
impl From<&PrivateKey> for public::KeyData {
fn from(private_key: &PrivateKey) -> public::KeyData {
private_key.public_key.key_data.clone()
}
}
#[cfg(feature = "alloc")]
impl From<DsaKeypair> for PrivateKey {
fn from(keypair: DsaKeypair) -> PrivateKey {
KeypairData::from(keypair)
.try_into()
.expect(CONVERSION_ERROR_MSG)
}
}
#[cfg(feature = "ecdsa")]
impl From<EcdsaKeypair> for PrivateKey {
fn from(keypair: EcdsaKeypair) -> PrivateKey {
KeypairData::from(keypair)
.try_into()
.expect(CONVERSION_ERROR_MSG)
}
}
impl From<Ed25519Keypair> for PrivateKey {
fn from(keypair: Ed25519Keypair) -> PrivateKey {
KeypairData::from(keypair)
.try_into()
.expect(CONVERSION_ERROR_MSG)
}
}
#[cfg(feature = "alloc")]
impl From<RsaKeypair> for PrivateKey {
fn from(keypair: RsaKeypair) -> PrivateKey {
KeypairData::from(keypair)
.try_into()
.expect(CONVERSION_ERROR_MSG)
}
}
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
impl From<SkEcdsaSha2NistP256> for PrivateKey {
fn from(keypair: SkEcdsaSha2NistP256) -> PrivateKey {
KeypairData::from(keypair)
.try_into()
.expect(CONVERSION_ERROR_MSG)
}
}
#[cfg(feature = "alloc")]
impl From<SkEd25519> for PrivateKey {
fn from(keypair: SkEd25519) -> PrivateKey {
KeypairData::from(keypair)
.try_into()
.expect(CONVERSION_ERROR_MSG)
}
}
impl TryFrom<KeypairData> for PrivateKey {
type Error = Error;
fn try_from(key_data: KeypairData) -> Result<PrivateKey> {
let public_key = public::KeyData::try_from(&key_data)?;
Ok(Self {
cipher: Cipher::None,
kdf: Kdf::None,
checkint: None,
public_key: public_key.into(),
key_data,
auth_tag: None,
})
}
}
impl PemLabel for PrivateKey {
const PEM_LABEL: &'static str = "OPENSSH PRIVATE KEY";
}
impl str::FromStr for PrivateKey {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::from_openssh(s)
}
}

245
vendor/ssh-key/src/private/dsa.rs vendored Normal file
View File

@@ -0,0 +1,245 @@
//! Digital Signature Algorithm (DSA) private keys.
use crate::{public::DsaPublicKey, Error, Mpint, Result};
use core::fmt;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use subtle::{Choice, ConstantTimeEq};
use zeroize::Zeroize;
#[cfg(all(feature = "dsa", feature = "rand_core"))]
use rand_core::CryptoRngCore;
/// Digital Signature Algorithm (DSA) private key.
///
/// Uniformly random integer `x`, such that `0 < x < q`, i.e. `x` is in the
/// range `[1, q1]`.
///
/// Described in [FIPS 186-4 § 4.1](https://csrc.nist.gov/publications/detail/fips/186/4/final).
#[derive(Clone)]
pub struct DsaPrivateKey {
/// Integer representing a DSA private key.
inner: Mpint,
}
impl DsaPrivateKey {
/// Get the serialized private key as bytes.
pub fn as_bytes(&self) -> &[u8] {
self.inner.as_bytes()
}
/// Get the inner [`Mpint`].
pub fn as_mpint(&self) -> &Mpint {
&self.inner
}
}
impl AsRef<[u8]> for DsaPrivateKey {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl ConstantTimeEq for DsaPrivateKey {
fn ct_eq(&self, other: &Self) -> Choice {
self.inner.ct_eq(&other.inner)
}
}
impl Eq for DsaPrivateKey {}
impl PartialEq for DsaPrivateKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Decode for DsaPrivateKey {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
Ok(Self {
inner: Mpint::decode(reader)?,
})
}
}
impl Encode for DsaPrivateKey {
fn encoded_len(&self) -> encoding::Result<usize> {
self.inner.encoded_len()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.inner.encode(writer)
}
}
impl fmt::Debug for DsaPrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DsaPrivateKey").finish_non_exhaustive()
}
}
impl Drop for DsaPrivateKey {
fn drop(&mut self) {
self.inner.zeroize();
}
}
#[cfg(feature = "dsa")]
impl TryFrom<DsaPrivateKey> for dsa::BigUint {
type Error = Error;
fn try_from(key: DsaPrivateKey) -> Result<dsa::BigUint> {
dsa::BigUint::try_from(&key.inner)
}
}
#[cfg(feature = "dsa")]
impl TryFrom<&DsaPrivateKey> for dsa::BigUint {
type Error = Error;
fn try_from(key: &DsaPrivateKey) -> Result<dsa::BigUint> {
dsa::BigUint::try_from(&key.inner)
}
}
#[cfg(feature = "dsa")]
impl TryFrom<dsa::SigningKey> for DsaPrivateKey {
type Error = Error;
fn try_from(key: dsa::SigningKey) -> Result<DsaPrivateKey> {
DsaPrivateKey::try_from(&key)
}
}
#[cfg(feature = "dsa")]
impl TryFrom<&dsa::SigningKey> for DsaPrivateKey {
type Error = Error;
fn try_from(key: &dsa::SigningKey) -> Result<DsaPrivateKey> {
Ok(DsaPrivateKey {
inner: key.x().try_into()?,
})
}
}
/// Digital Signature Algorithm (DSA) private/public keypair.
#[derive(Clone)]
pub struct DsaKeypair {
/// Public key.
pub public: DsaPublicKey,
/// Private key.
pub private: DsaPrivateKey,
}
impl DsaKeypair {
/// Key size.
#[cfg(all(feature = "dsa", feature = "rand_core"))]
#[allow(deprecated)]
pub(crate) const KEY_SIZE: dsa::KeySize = dsa::KeySize::DSA_1024_160;
/// Generate a random DSA private key.
#[cfg(all(feature = "dsa", feature = "rand_core"))]
pub fn random(rng: &mut impl CryptoRngCore) -> Result<Self> {
let components = dsa::Components::generate(rng, Self::KEY_SIZE);
dsa::SigningKey::generate(rng, components).try_into()
}
}
impl ConstantTimeEq for DsaKeypair {
fn ct_eq(&self, other: &Self) -> Choice {
Choice::from((self.public == other.public) as u8) & self.private.ct_eq(&other.private)
}
}
impl PartialEq for DsaKeypair {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Eq for DsaKeypair {}
impl Decode for DsaKeypair {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let public = DsaPublicKey::decode(reader)?;
let private = DsaPrivateKey::decode(reader)?;
Ok(DsaKeypair { public, private })
}
}
impl Encode for DsaKeypair {
fn encoded_len(&self) -> encoding::Result<usize> {
[self.public.encoded_len()?, self.private.encoded_len()?].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.public.encode(writer)?;
self.private.encode(writer)
}
}
impl From<DsaKeypair> for DsaPublicKey {
fn from(keypair: DsaKeypair) -> DsaPublicKey {
keypair.public
}
}
impl From<&DsaKeypair> for DsaPublicKey {
fn from(keypair: &DsaKeypair) -> DsaPublicKey {
keypair.public.clone()
}
}
impl fmt::Debug for DsaKeypair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DsaKeypair")
.field("public", &self.public)
.finish_non_exhaustive()
}
}
#[cfg(feature = "dsa")]
impl TryFrom<DsaKeypair> for dsa::SigningKey {
type Error = Error;
fn try_from(key: DsaKeypair) -> Result<dsa::SigningKey> {
dsa::SigningKey::try_from(&key)
}
}
#[cfg(feature = "dsa")]
impl TryFrom<&DsaKeypair> for dsa::SigningKey {
type Error = Error;
fn try_from(key: &DsaKeypair) -> Result<dsa::SigningKey> {
Ok(dsa::SigningKey::from_components(
dsa::VerifyingKey::try_from(&key.public)?,
dsa::BigUint::try_from(&key.private)?,
)?)
}
}
#[cfg(feature = "dsa")]
impl TryFrom<dsa::SigningKey> for DsaKeypair {
type Error = Error;
fn try_from(key: dsa::SigningKey) -> Result<DsaKeypair> {
DsaKeypair::try_from(&key)
}
}
#[cfg(feature = "dsa")]
impl TryFrom<&dsa::SigningKey> for DsaKeypair {
type Error = Error;
fn try_from(key: &dsa::SigningKey) -> Result<DsaKeypair> {
Ok(DsaKeypair {
private: key.try_into()?,
public: key.verifying_key().try_into()?,
})
}
}

348
vendor/ssh-key/src/private/ecdsa.rs vendored Normal file
View File

@@ -0,0 +1,348 @@
//! Elliptic Curve Digital Signature Algorithm (ECDSA) private keys.
use crate::{public::EcdsaPublicKey, Algorithm, EcdsaCurve, Error, Result};
use core::fmt;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use sec1::consts::{U32, U48, U66};
use subtle::{Choice, ConstantTimeEq};
use zeroize::Zeroize;
#[cfg(feature = "rand_core")]
use rand_core::CryptoRngCore;
/// Elliptic Curve Digital Signature Algorithm (ECDSA) private key.
#[derive(Clone)]
pub struct EcdsaPrivateKey<const SIZE: usize> {
/// Byte array containing serialized big endian private scalar.
bytes: [u8; SIZE],
}
impl<const SIZE: usize> EcdsaPrivateKey<SIZE> {
/// Borrow the inner byte array as a slice.
pub fn as_slice(&self) -> &[u8] {
self.bytes.as_ref()
}
/// Convert to the inner byte array.
pub fn into_bytes(self) -> [u8; SIZE] {
self.bytes
}
/// Does this private key need to be prefixed with a leading zero?
fn needs_leading_zero(&self) -> bool {
self.bytes[0] >= 0x80
}
}
impl<const SIZE: usize> Decode for EcdsaPrivateKey<SIZE> {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
reader.read_prefixed(|reader| {
if reader.remaining_len() == SIZE.checked_add(1).ok_or(encoding::Error::Length)? {
// Strip leading zero
// TODO(tarcieri): make sure leading zero was necessary
if u8::decode(reader)? != 0 {
return Err(Error::FormatEncoding);
}
}
let mut bytes = [0u8; SIZE];
reader.read(&mut bytes)?;
Ok(Self { bytes })
})
}
}
impl<const SIZE: usize> Encode for EcdsaPrivateKey<SIZE> {
fn encoded_len(&self) -> encoding::Result<usize> {
[4, self.needs_leading_zero().into(), SIZE].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
[self.needs_leading_zero().into(), SIZE]
.checked_sum()?
.encode(writer)?;
if self.needs_leading_zero() {
writer.write(&[0])?;
}
writer.write(&self.bytes)?;
Ok(())
}
}
impl<const SIZE: usize> AsRef<[u8; SIZE]> for EcdsaPrivateKey<SIZE> {
fn as_ref(&self) -> &[u8; SIZE] {
&self.bytes
}
}
impl<const SIZE: usize> ConstantTimeEq for EcdsaPrivateKey<SIZE> {
fn ct_eq(&self, other: &Self) -> Choice {
self.as_ref().ct_eq(other.as_ref())
}
}
impl<const SIZE: usize> PartialEq for EcdsaPrivateKey<SIZE> {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl<const SIZE: usize> Eq for EcdsaPrivateKey<SIZE> {}
impl<const SIZE: usize> fmt::Debug for EcdsaPrivateKey<SIZE> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EcdsaPrivateKey").finish_non_exhaustive()
}
}
impl<const SIZE: usize> fmt::LowerHex for EcdsaPrivateKey<SIZE> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_ref() {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
impl<const SIZE: usize> fmt::UpperHex for EcdsaPrivateKey<SIZE> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_ref() {
write!(f, "{byte:02X}")?;
}
Ok(())
}
}
impl<const SIZE: usize> Drop for EcdsaPrivateKey<SIZE> {
fn drop(&mut self) {
self.bytes.zeroize();
}
}
#[cfg(feature = "p256")]
impl From<p256::SecretKey> for EcdsaPrivateKey<32> {
fn from(sk: p256::SecretKey) -> EcdsaPrivateKey<32> {
EcdsaPrivateKey {
bytes: sk.to_bytes().into(),
}
}
}
#[cfg(feature = "p384")]
impl From<p384::SecretKey> for EcdsaPrivateKey<48> {
fn from(sk: p384::SecretKey) -> EcdsaPrivateKey<48> {
EcdsaPrivateKey {
bytes: sk.to_bytes().into(),
}
}
}
#[cfg(feature = "p521")]
impl From<p521::SecretKey> for EcdsaPrivateKey<66> {
fn from(sk: p521::SecretKey) -> EcdsaPrivateKey<66> {
// TODO(tarcieri): clean this up when migrating to hybrid-array
let mut bytes = [0u8; 66];
bytes.copy_from_slice(&sk.to_bytes());
EcdsaPrivateKey { bytes }
}
}
/// Elliptic Curve Digital Signature Algorithm (ECDSA) private/public keypair.
#[derive(Clone, Debug)]
pub enum EcdsaKeypair {
/// NIST P-256 ECDSA keypair.
NistP256 {
/// Public key.
public: sec1::EncodedPoint<U32>,
/// Private key.
private: EcdsaPrivateKey<32>,
},
/// NIST P-384 ECDSA keypair.
NistP384 {
/// Public key.
public: sec1::EncodedPoint<U48>,
/// Private key.
private: EcdsaPrivateKey<48>,
},
/// NIST P-521 ECDSA keypair.
NistP521 {
/// Public key.
public: sec1::EncodedPoint<U66>,
/// Private key.
private: EcdsaPrivateKey<66>,
},
}
impl EcdsaKeypair {
/// Generate a random ECDSA private key.
#[cfg(feature = "rand_core")]
#[allow(unused_variables)]
pub fn random(rng: &mut impl CryptoRngCore, curve: EcdsaCurve) -> Result<Self> {
match curve {
#[cfg(feature = "p256")]
EcdsaCurve::NistP256 => {
let private = p256::SecretKey::random(rng);
let public = private.public_key();
Ok(EcdsaKeypair::NistP256 {
private: private.into(),
public: public.into(),
})
}
#[cfg(feature = "p384")]
EcdsaCurve::NistP384 => {
let private = p384::SecretKey::random(rng);
let public = private.public_key();
Ok(EcdsaKeypair::NistP384 {
private: private.into(),
public: public.into(),
})
}
#[cfg(feature = "p521")]
EcdsaCurve::NistP521 => {
let private = p521::SecretKey::random(rng);
let public = private.public_key();
Ok(EcdsaKeypair::NistP521 {
private: private.into(),
public: public.into(),
})
}
#[cfg(not(all(feature = "p256", feature = "p384", feature = "p521")))]
_ => Err(Error::AlgorithmUnknown),
}
}
/// Get the [`Algorithm`] for this public key type.
pub fn algorithm(&self) -> Algorithm {
Algorithm::Ecdsa {
curve: self.curve(),
}
}
/// Get the [`EcdsaCurve`] for this key.
pub fn curve(&self) -> EcdsaCurve {
match self {
Self::NistP256 { .. } => EcdsaCurve::NistP256,
Self::NistP384 { .. } => EcdsaCurve::NistP384,
Self::NistP521 { .. } => EcdsaCurve::NistP521,
}
}
/// Get the bytes representing the public key.
pub fn public_key_bytes(&self) -> &[u8] {
match self {
Self::NistP256 { public, .. } => public.as_ref(),
Self::NistP384 { public, .. } => public.as_ref(),
Self::NistP521 { public, .. } => public.as_ref(),
}
}
/// Get the bytes representing the private key.
pub fn private_key_bytes(&self) -> &[u8] {
match self {
Self::NistP256 { private, .. } => private.as_ref(),
Self::NistP384 { private, .. } => private.as_ref(),
Self::NistP521 { private, .. } => private.as_ref(),
}
}
}
impl ConstantTimeEq for EcdsaKeypair {
fn ct_eq(&self, other: &Self) -> Choice {
let public_eq =
Choice::from((EcdsaPublicKey::from(self) == EcdsaPublicKey::from(other)) as u8);
let private_key_a = match self {
Self::NistP256 { private, .. } => private.as_slice(),
Self::NistP384 { private, .. } => private.as_slice(),
Self::NistP521 { private, .. } => private.as_slice(),
};
let private_key_b = match other {
Self::NistP256 { private, .. } => private.as_slice(),
Self::NistP384 { private, .. } => private.as_slice(),
Self::NistP521 { private, .. } => private.as_slice(),
};
public_eq & private_key_a.ct_eq(private_key_b)
}
}
impl Eq for EcdsaKeypair {}
impl PartialEq for EcdsaKeypair {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Decode for EcdsaKeypair {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
match EcdsaPublicKey::decode(reader)? {
EcdsaPublicKey::NistP256(public) => {
let private = EcdsaPrivateKey::<32>::decode(reader)?;
Ok(Self::NistP256 { public, private })
}
EcdsaPublicKey::NistP384(public) => {
let private = EcdsaPrivateKey::<48>::decode(reader)?;
Ok(Self::NistP384 { public, private })
}
EcdsaPublicKey::NistP521(public) => {
let private = EcdsaPrivateKey::<66>::decode(reader)?;
Ok(Self::NistP521 { public, private })
}
}
}
}
impl Encode for EcdsaKeypair {
fn encoded_len(&self) -> encoding::Result<usize> {
let public_len = EcdsaPublicKey::from(self).encoded_len()?;
let private_len = match self {
Self::NistP256 { private, .. } => private.encoded_len()?,
Self::NistP384 { private, .. } => private.encoded_len()?,
Self::NistP521 { private, .. } => private.encoded_len()?,
};
[public_len, private_len].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
EcdsaPublicKey::from(self).encode(writer)?;
match self {
Self::NistP256 { private, .. } => private.encode(writer)?,
Self::NistP384 { private, .. } => private.encode(writer)?,
Self::NistP521 { private, .. } => private.encode(writer)?,
}
Ok(())
}
}
impl From<EcdsaKeypair> for EcdsaPublicKey {
fn from(keypair: EcdsaKeypair) -> EcdsaPublicKey {
EcdsaPublicKey::from(&keypair)
}
}
impl From<&EcdsaKeypair> for EcdsaPublicKey {
fn from(keypair: &EcdsaKeypair) -> EcdsaPublicKey {
match keypair {
EcdsaKeypair::NistP256 { public, .. } => EcdsaPublicKey::NistP256(*public),
EcdsaKeypair::NistP384 { public, .. } => EcdsaPublicKey::NistP384(*public),
EcdsaKeypair::NistP521 { public, .. } => EcdsaPublicKey::NistP521(*public),
}
}
}

322
vendor/ssh-key/src/private/ed25519.rs vendored Normal file
View File

@@ -0,0 +1,322 @@
//! Ed25519 private keys.
//!
//! Edwards Digital Signature Algorithm (EdDSA) over Curve25519.
use crate::{public::Ed25519PublicKey, Error, Result};
use core::fmt;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use subtle::{Choice, ConstantTimeEq};
use zeroize::{Zeroize, Zeroizing};
#[cfg(feature = "rand_core")]
use rand_core::CryptoRngCore;
/// Ed25519 private key.
// TODO(tarcieri): use `ed25519::PrivateKey`? (doesn't exist yet)
#[derive(Clone)]
pub struct Ed25519PrivateKey([u8; Self::BYTE_SIZE]);
impl Ed25519PrivateKey {
/// Size of an Ed25519 private key in bytes.
pub const BYTE_SIZE: usize = 32;
/// Generate a random Ed25519 private key.
#[cfg(feature = "rand_core")]
pub fn random(rng: &mut impl CryptoRngCore) -> Self {
let mut key_bytes = [0u8; Self::BYTE_SIZE];
rng.fill_bytes(&mut key_bytes);
Self(key_bytes)
}
/// Parse Ed25519 private key from bytes.
pub fn from_bytes(bytes: &[u8; Self::BYTE_SIZE]) -> Self {
Self(*bytes)
}
/// Convert to the inner byte array.
pub fn to_bytes(&self) -> [u8; Self::BYTE_SIZE] {
self.0
}
}
impl AsRef<[u8; Self::BYTE_SIZE]> for Ed25519PrivateKey {
fn as_ref(&self) -> &[u8; Self::BYTE_SIZE] {
&self.0
}
}
impl ConstantTimeEq for Ed25519PrivateKey {
fn ct_eq(&self, other: &Self) -> Choice {
self.as_ref().ct_eq(other.as_ref())
}
}
impl Eq for Ed25519PrivateKey {}
impl PartialEq for Ed25519PrivateKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl TryFrom<&[u8]> for Ed25519PrivateKey {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Self> {
Ok(Ed25519PrivateKey::from_bytes(bytes.try_into()?))
}
}
impl fmt::Debug for Ed25519PrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Ed25519PrivateKey").finish_non_exhaustive()
}
}
impl fmt::LowerHex for Ed25519PrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_ref() {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
impl fmt::UpperHex for Ed25519PrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_ref() {
write!(f, "{byte:02X}")?;
}
Ok(())
}
}
impl Drop for Ed25519PrivateKey {
fn drop(&mut self) {
self.0.zeroize();
}
}
#[cfg(feature = "ed25519")]
impl From<Ed25519PrivateKey> for ed25519_dalek::SigningKey {
fn from(key: Ed25519PrivateKey) -> ed25519_dalek::SigningKey {
ed25519_dalek::SigningKey::from(&key)
}
}
#[cfg(feature = "ed25519")]
impl From<&Ed25519PrivateKey> for ed25519_dalek::SigningKey {
fn from(key: &Ed25519PrivateKey) -> ed25519_dalek::SigningKey {
ed25519_dalek::SigningKey::from_bytes(key.as_ref())
}
}
#[cfg(feature = "ed25519")]
impl From<ed25519_dalek::SigningKey> for Ed25519PrivateKey {
fn from(key: ed25519_dalek::SigningKey) -> Ed25519PrivateKey {
Ed25519PrivateKey::from(&key)
}
}
#[cfg(feature = "ed25519")]
impl From<&ed25519_dalek::SigningKey> for Ed25519PrivateKey {
fn from(key: &ed25519_dalek::SigningKey) -> Ed25519PrivateKey {
Ed25519PrivateKey(key.to_bytes())
}
}
#[cfg(feature = "ed25519")]
impl From<Ed25519PrivateKey> for Ed25519PublicKey {
fn from(private: Ed25519PrivateKey) -> Ed25519PublicKey {
Ed25519PublicKey::from(&private)
}
}
#[cfg(feature = "ed25519")]
impl From<&Ed25519PrivateKey> for Ed25519PublicKey {
fn from(private: &Ed25519PrivateKey) -> Ed25519PublicKey {
ed25519_dalek::SigningKey::from(private)
.verifying_key()
.into()
}
}
/// Ed25519 private/public keypair.
#[derive(Clone)]
pub struct Ed25519Keypair {
/// Public key.
pub public: Ed25519PublicKey,
/// Private key.
pub private: Ed25519PrivateKey,
}
impl Ed25519Keypair {
/// Size of an Ed25519 keypair in bytes.
pub const BYTE_SIZE: usize = 64;
/// Generate a random Ed25519 private keypair.
#[cfg(feature = "ed25519")]
pub fn random(rng: &mut impl CryptoRngCore) -> Self {
Ed25519PrivateKey::random(rng).into()
}
/// Expand a keypair from a 32-byte seed value.
#[cfg(feature = "ed25519")]
pub fn from_seed(seed: &[u8; Ed25519PrivateKey::BYTE_SIZE]) -> Self {
Ed25519PrivateKey::from_bytes(seed).into()
}
/// Parse Ed25519 keypair from 64-bytes which comprise the serialized
/// private and public keys.
pub fn from_bytes(bytes: &[u8; Self::BYTE_SIZE]) -> Result<Self> {
let (priv_bytes, pub_bytes) = bytes.split_at(Ed25519PrivateKey::BYTE_SIZE);
let private = Ed25519PrivateKey::try_from(priv_bytes)?;
let public = Ed25519PublicKey::try_from(pub_bytes)?;
// Validate the public key if possible
#[cfg(feature = "ed25519")]
if Ed25519PublicKey::from(&private) != public {
return Err(Error::Crypto);
}
Ok(Ed25519Keypair { private, public })
}
/// Serialize an Ed25519 keypair as bytes.
pub fn to_bytes(&self) -> [u8; Self::BYTE_SIZE] {
let mut result = [0u8; Self::BYTE_SIZE];
result[..(Self::BYTE_SIZE / 2)].copy_from_slice(self.private.as_ref());
result[(Self::BYTE_SIZE / 2)..].copy_from_slice(self.public.as_ref());
result
}
}
impl ConstantTimeEq for Ed25519Keypair {
fn ct_eq(&self, other: &Self) -> Choice {
Choice::from((self.public == other.public) as u8) & self.private.ct_eq(&other.private)
}
}
impl Eq for Ed25519Keypair {}
impl PartialEq for Ed25519Keypair {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Decode for Ed25519Keypair {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
// Decode private key
let public = Ed25519PublicKey::decode(reader)?;
// The OpenSSH serialization of Ed25519 keys is repetitive and includes
// a serialization of `private_key[32] || public_key[32]` immediately
// following the public key.
let mut bytes = Zeroizing::new([0u8; Self::BYTE_SIZE]);
reader.read_prefixed(|reader| reader.read(&mut *bytes))?;
let keypair = Self::from_bytes(&bytes)?;
// Ensure public key matches the one one the keypair
if keypair.public == public {
Ok(keypair)
} else {
Err(Error::Crypto)
}
}
}
impl Encode for Ed25519Keypair {
fn encoded_len(&self) -> encoding::Result<usize> {
[4, self.public.encoded_len()?, Self::BYTE_SIZE].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.public.encode(writer)?;
Zeroizing::new(self.to_bytes()).as_ref().encode(writer)?;
Ok(())
}
}
impl From<Ed25519Keypair> for Ed25519PublicKey {
fn from(keypair: Ed25519Keypair) -> Ed25519PublicKey {
keypair.public
}
}
impl From<&Ed25519Keypair> for Ed25519PublicKey {
fn from(keypair: &Ed25519Keypair) -> Ed25519PublicKey {
keypair.public
}
}
impl TryFrom<&[u8]> for Ed25519Keypair {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Self> {
Ed25519Keypair::from_bytes(bytes.try_into()?)
}
}
impl fmt::Debug for Ed25519Keypair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Ed25519Keypair")
.field("public", &self.public)
.finish_non_exhaustive()
}
}
#[cfg(feature = "ed25519")]
impl From<Ed25519PrivateKey> for Ed25519Keypair {
fn from(private: Ed25519PrivateKey) -> Ed25519Keypair {
let secret = ed25519_dalek::SigningKey::from(&private);
let public = secret.verifying_key().into();
Ed25519Keypair { private, public }
}
}
#[cfg(feature = "ed25519")]
impl TryFrom<Ed25519Keypair> for ed25519_dalek::SigningKey {
type Error = Error;
fn try_from(key: Ed25519Keypair) -> Result<ed25519_dalek::SigningKey> {
ed25519_dalek::SigningKey::try_from(&key)
}
}
#[cfg(feature = "ed25519")]
impl TryFrom<&Ed25519Keypair> for ed25519_dalek::SigningKey {
type Error = Error;
fn try_from(key: &Ed25519Keypair) -> Result<ed25519_dalek::SigningKey> {
let signing_key = ed25519_dalek::SigningKey::from(&key.private);
let verifying_key = ed25519_dalek::VerifyingKey::try_from(&key.public)?;
if signing_key.verifying_key() == verifying_key {
Ok(signing_key)
} else {
Err(Error::PublicKey)
}
}
}
#[cfg(feature = "ed25519")]
impl From<ed25519_dalek::SigningKey> for Ed25519Keypair {
fn from(key: ed25519_dalek::SigningKey) -> Ed25519Keypair {
Ed25519Keypair::from(&key)
}
}
#[cfg(feature = "ed25519")]
impl From<&ed25519_dalek::SigningKey> for Ed25519Keypair {
fn from(key: &ed25519_dalek::SigningKey) -> Ed25519Keypair {
Ed25519Keypair {
private: key.into(),
public: key.verifying_key().into(),
}
}
}

444
vendor/ssh-key/src/private/keypair.rs vendored Normal file
View File

@@ -0,0 +1,444 @@
//! Private key pairs.
use super::ed25519::Ed25519Keypair;
use crate::{public, Algorithm, Error, Result};
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use subtle::{Choice, ConstantTimeEq};
#[cfg(feature = "alloc")]
use {
super::{DsaKeypair, OpaqueKeypair, RsaKeypair, SkEd25519},
alloc::vec::Vec,
};
#[cfg(feature = "ecdsa")]
use super::EcdsaKeypair;
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
use super::SkEcdsaSha2NistP256;
/// Private key data: digital signature key pairs.
///
/// SSH private keys contain pairs of public and private keys for various
/// supported digital signature algorithms.
// TODO(tarcieri): pseudo-private keys for FIDO/U2F security keys
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum KeypairData {
/// Digital Signature Algorithm (DSA) keypair.
#[cfg(feature = "alloc")]
Dsa(DsaKeypair),
/// ECDSA keypair.
#[cfg(feature = "ecdsa")]
Ecdsa(EcdsaKeypair),
/// Ed25519 keypair.
Ed25519(Ed25519Keypair),
/// Encrypted private key (ciphertext).
#[cfg(feature = "alloc")]
Encrypted(Vec<u8>),
/// RSA keypair.
#[cfg(feature = "alloc")]
Rsa(RsaKeypair),
/// Security Key (FIDO/U2F) using ECDSA/NIST P-256 as specified in [PROTOCOL.u2f].
///
/// [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
SkEcdsaSha2NistP256(SkEcdsaSha2NistP256),
/// Security Key (FIDO/U2F) using Ed25519 as specified in [PROTOCOL.u2f].
///
/// [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
#[cfg(feature = "alloc")]
SkEd25519(SkEd25519),
/// Opaque keypair.
#[cfg(feature = "alloc")]
Other(OpaqueKeypair),
}
impl KeypairData {
/// Get the [`Algorithm`] for this private key.
pub fn algorithm(&self) -> Result<Algorithm> {
Ok(match self {
#[cfg(feature = "alloc")]
Self::Dsa(_) => Algorithm::Dsa,
#[cfg(feature = "ecdsa")]
Self::Ecdsa(key) => key.algorithm(),
Self::Ed25519(_) => Algorithm::Ed25519,
#[cfg(feature = "alloc")]
Self::Encrypted(_) => return Err(Error::Encrypted),
#[cfg(feature = "alloc")]
Self::Rsa(_) => Algorithm::Rsa { hash: None },
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
Self::SkEcdsaSha2NistP256(_) => Algorithm::SkEcdsaSha2NistP256,
#[cfg(feature = "alloc")]
Self::SkEd25519(_) => Algorithm::SkEd25519,
#[cfg(feature = "alloc")]
Self::Other(key) => key.algorithm(),
})
}
/// Get DSA keypair if this key is the correct type.
#[cfg(feature = "alloc")]
pub fn dsa(&self) -> Option<&DsaKeypair> {
match self {
Self::Dsa(key) => Some(key),
_ => None,
}
}
/// Get ECDSA private key if this key is the correct type.
#[cfg(feature = "ecdsa")]
pub fn ecdsa(&self) -> Option<&EcdsaKeypair> {
match self {
Self::Ecdsa(keypair) => Some(keypair),
_ => None,
}
}
/// Get Ed25519 private key if this key is the correct type.
pub fn ed25519(&self) -> Option<&Ed25519Keypair> {
match self {
Self::Ed25519(key) => Some(key),
#[allow(unreachable_patterns)]
_ => None,
}
}
/// Get the encrypted ciphertext if this key is encrypted.
#[cfg(feature = "alloc")]
pub fn encrypted(&self) -> Option<&[u8]> {
match self {
Self::Encrypted(ciphertext) => Some(ciphertext),
_ => None,
}
}
/// Get RSA keypair if this key is the correct type.
#[cfg(feature = "alloc")]
pub fn rsa(&self) -> Option<&RsaKeypair> {
match self {
Self::Rsa(key) => Some(key),
_ => None,
}
}
/// Get FIDO/U2F ECDSA/NIST P-256 private key if this key is the correct type.
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
pub fn sk_ecdsa_p256(&self) -> Option<&SkEcdsaSha2NistP256> {
match self {
Self::SkEcdsaSha2NistP256(sk) => Some(sk),
_ => None,
}
}
/// Get FIDO/U2F Ed25519 private key if this key is the correct type.
#[cfg(feature = "alloc")]
pub fn sk_ed25519(&self) -> Option<&SkEd25519> {
match self {
Self::SkEd25519(sk) => Some(sk),
_ => None,
}
}
/// Get the custom, opaque private key if this key is the correct type.
#[cfg(feature = "alloc")]
pub fn other(&self) -> Option<&OpaqueKeypair> {
match self {
Self::Other(key) => Some(key),
_ => None,
}
}
/// Is this key a DSA key?
#[cfg(feature = "alloc")]
pub fn is_dsa(&self) -> bool {
matches!(self, Self::Dsa(_))
}
/// Is this key an ECDSA key?
#[cfg(feature = "ecdsa")]
pub fn is_ecdsa(&self) -> bool {
matches!(self, Self::Ecdsa(_))
}
/// Is this key an Ed25519 key?
pub fn is_ed25519(&self) -> bool {
matches!(self, Self::Ed25519(_))
}
/// Is this key encrypted?
#[cfg(not(feature = "alloc"))]
pub fn is_encrypted(&self) -> bool {
false
}
/// Is this key encrypted?
#[cfg(feature = "alloc")]
pub fn is_encrypted(&self) -> bool {
matches!(self, Self::Encrypted(_))
}
/// Is this key an RSA key?
#[cfg(feature = "alloc")]
pub fn is_rsa(&self) -> bool {
matches!(self, Self::Rsa(_))
}
/// Is this key a FIDO/U2F ECDSA/NIST P-256 key?
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
pub fn is_sk_ecdsa_p256(&self) -> bool {
matches!(self, Self::SkEcdsaSha2NistP256(_))
}
/// Is this key a FIDO/U2F Ed25519 key?
#[cfg(feature = "alloc")]
pub fn is_sk_ed25519(&self) -> bool {
matches!(self, Self::SkEd25519(_))
}
/// Is this a key with a custom algorithm?
#[cfg(feature = "alloc")]
pub fn is_other(&self) -> bool {
matches!(self, Self::Other(_))
}
/// Compute a deterministic "checkint" for this private key.
///
/// This is a sort of primitive pseudo-MAC used by the OpenSSH key format.
// TODO(tarcieri): true randomness or a better algorithm?
pub(super) fn checkint(&self) -> u32 {
let bytes = match self {
#[cfg(feature = "alloc")]
Self::Dsa(dsa) => dsa.private.as_bytes(),
#[cfg(feature = "ecdsa")]
Self::Ecdsa(ecdsa) => ecdsa.private_key_bytes(),
Self::Ed25519(ed25519) => ed25519.private.as_ref(),
#[cfg(feature = "alloc")]
Self::Encrypted(ciphertext) => ciphertext.as_ref(),
#[cfg(feature = "alloc")]
Self::Rsa(rsa) => rsa.private.d.as_bytes(),
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
Self::SkEcdsaSha2NistP256(sk) => sk.key_handle(),
#[cfg(feature = "alloc")]
Self::SkEd25519(sk) => sk.key_handle(),
#[cfg(feature = "alloc")]
Self::Other(key) => key.private.as_ref(),
};
let mut n = 0u32;
for chunk in bytes.chunks_exact(4) {
n ^= u32::from_be_bytes(chunk.try_into().expect("not 4 bytes"));
}
n
}
/// Decode [`KeypairData`] for the specified algorithm.
pub fn decode_as(reader: &mut impl Reader, algorithm: Algorithm) -> Result<Self> {
match algorithm {
#[cfg(feature = "alloc")]
Algorithm::Dsa => DsaKeypair::decode(reader).map(Self::Dsa),
#[cfg(feature = "ecdsa")]
Algorithm::Ecdsa { curve } => match EcdsaKeypair::decode(reader)? {
keypair if keypair.curve() == curve => Ok(Self::Ecdsa(keypair)),
_ => Err(Error::AlgorithmUnknown),
},
Algorithm::Ed25519 => Ed25519Keypair::decode(reader).map(Self::Ed25519),
#[cfg(feature = "alloc")]
Algorithm::Rsa { .. } => RsaKeypair::decode(reader).map(Self::Rsa),
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
Algorithm::SkEcdsaSha2NistP256 => {
SkEcdsaSha2NistP256::decode(reader).map(Self::SkEcdsaSha2NistP256)
}
#[cfg(feature = "alloc")]
Algorithm::SkEd25519 => SkEd25519::decode(reader).map(Self::SkEd25519),
#[cfg(feature = "alloc")]
algorithm @ Algorithm::Other(_) => {
OpaqueKeypair::decode_as(reader, algorithm).map(Self::Other)
}
#[allow(unreachable_patterns)]
_ => Err(Error::AlgorithmUnknown),
}
}
}
impl ConstantTimeEq for KeypairData {
fn ct_eq(&self, other: &Self) -> Choice {
// Note: constant-time with respect to key *data* comparisons, not algorithms
match (self, other) {
#[cfg(feature = "alloc")]
(Self::Dsa(a), Self::Dsa(b)) => a.ct_eq(b),
#[cfg(feature = "ecdsa")]
(Self::Ecdsa(a), Self::Ecdsa(b)) => a.ct_eq(b),
(Self::Ed25519(a), Self::Ed25519(b)) => a.ct_eq(b),
#[cfg(feature = "alloc")]
(Self::Encrypted(a), Self::Encrypted(b)) => a.ct_eq(b),
#[cfg(feature = "alloc")]
(Self::Rsa(a), Self::Rsa(b)) => a.ct_eq(b),
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
(Self::SkEcdsaSha2NistP256(a), Self::SkEcdsaSha2NistP256(b)) => {
// Security Keys store the actual private key in hardware.
// The key structs contain all public data.
Choice::from((a == b) as u8)
}
#[cfg(feature = "alloc")]
(Self::SkEd25519(a), Self::SkEd25519(b)) => {
// Security Keys store the actual private key in hardware.
// The key structs contain all public data.
Choice::from((a == b) as u8)
}
#[cfg(feature = "alloc")]
(Self::Other(a), Self::Other(b)) => a.ct_eq(b),
#[allow(unreachable_patterns)]
_ => Choice::from(0),
}
}
}
impl Eq for KeypairData {}
impl PartialEq for KeypairData {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Decode for KeypairData {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let algorithm = Algorithm::decode(reader)?;
Self::decode_as(reader, algorithm)
}
}
impl Encode for KeypairData {
fn encoded_len(&self) -> encoding::Result<usize> {
let alg_len = self
.algorithm()
.ok()
.map(|alg| alg.encoded_len())
.transpose()?
.unwrap_or(0);
let key_len = match self {
#[cfg(feature = "alloc")]
Self::Dsa(key) => key.encoded_len()?,
#[cfg(feature = "ecdsa")]
Self::Ecdsa(key) => key.encoded_len()?,
Self::Ed25519(key) => key.encoded_len()?,
#[cfg(feature = "alloc")]
Self::Encrypted(ciphertext) => return Ok(ciphertext.len()),
#[cfg(feature = "alloc")]
Self::Rsa(key) => key.encoded_len()?,
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
Self::SkEcdsaSha2NistP256(sk) => sk.encoded_len()?,
#[cfg(feature = "alloc")]
Self::SkEd25519(sk) => sk.encoded_len()?,
#[cfg(feature = "alloc")]
Self::Other(key) => key.encoded_len()?,
};
[alg_len, key_len].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
if let Ok(alg) = self.algorithm() {
alg.encode(writer)?;
}
match self {
#[cfg(feature = "alloc")]
Self::Dsa(key) => key.encode(writer)?,
#[cfg(feature = "ecdsa")]
Self::Ecdsa(key) => key.encode(writer)?,
Self::Ed25519(key) => key.encode(writer)?,
#[cfg(feature = "alloc")]
Self::Encrypted(ciphertext) => writer.write(ciphertext)?,
#[cfg(feature = "alloc")]
Self::Rsa(key) => key.encode(writer)?,
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
Self::SkEcdsaSha2NistP256(sk) => sk.encode(writer)?,
#[cfg(feature = "alloc")]
Self::SkEd25519(sk) => sk.encode(writer)?,
#[cfg(feature = "alloc")]
Self::Other(key) => key.encode(writer)?,
}
Ok(())
}
}
impl TryFrom<&KeypairData> for public::KeyData {
type Error = Error;
fn try_from(keypair_data: &KeypairData) -> Result<public::KeyData> {
Ok(match keypair_data {
#[cfg(feature = "alloc")]
KeypairData::Dsa(dsa) => public::KeyData::Dsa(dsa.into()),
#[cfg(feature = "ecdsa")]
KeypairData::Ecdsa(ecdsa) => public::KeyData::Ecdsa(ecdsa.into()),
KeypairData::Ed25519(ed25519) => public::KeyData::Ed25519(ed25519.into()),
#[cfg(feature = "alloc")]
KeypairData::Encrypted(_) => return Err(Error::Encrypted),
#[cfg(feature = "alloc")]
KeypairData::Rsa(rsa) => public::KeyData::Rsa(rsa.into()),
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
KeypairData::SkEcdsaSha2NistP256(sk) => {
public::KeyData::SkEcdsaSha2NistP256(sk.public().clone())
}
#[cfg(feature = "alloc")]
KeypairData::SkEd25519(sk) => public::KeyData::SkEd25519(sk.public().clone()),
#[cfg(feature = "alloc")]
KeypairData::Other(key) => public::KeyData::Other(key.into()),
})
}
}
#[cfg(feature = "alloc")]
impl From<DsaKeypair> for KeypairData {
fn from(keypair: DsaKeypair) -> KeypairData {
Self::Dsa(keypair)
}
}
#[cfg(feature = "ecdsa")]
impl From<EcdsaKeypair> for KeypairData {
fn from(keypair: EcdsaKeypair) -> KeypairData {
Self::Ecdsa(keypair)
}
}
impl From<Ed25519Keypair> for KeypairData {
fn from(keypair: Ed25519Keypair) -> KeypairData {
Self::Ed25519(keypair)
}
}
#[cfg(feature = "alloc")]
impl From<RsaKeypair> for KeypairData {
fn from(keypair: RsaKeypair) -> KeypairData {
Self::Rsa(keypair)
}
}
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
impl From<SkEcdsaSha2NistP256> for KeypairData {
fn from(keypair: SkEcdsaSha2NistP256) -> KeypairData {
Self::SkEcdsaSha2NistP256(keypair)
}
}
#[cfg(feature = "alloc")]
impl From<SkEd25519> for KeypairData {
fn from(keypair: SkEd25519) -> KeypairData {
Self::SkEd25519(keypair)
}
}

155
vendor/ssh-key/src/private/opaque.rs vendored Normal file
View File

@@ -0,0 +1,155 @@
//! Opaque private keys.
//!
//! [`OpaqueKeypair`] represents a keypair meant to be used with an algorithm unknown to this
//! crate, i.e. keypairs that use a custom algorithm as specified in [RFC4251 § 6].
//!
//! They are said to be opaque, because the meaning of their underlying byte representation is not
//! specified.
//!
//! [RFC4251 § 6]: https://www.rfc-editor.org/rfc/rfc4251.html#section-6
use crate::{
public::{OpaquePublicKey, OpaquePublicKeyBytes},
Algorithm, Error, Result,
};
use alloc::vec::Vec;
use core::fmt;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use subtle::{Choice, ConstantTimeEq};
/// An opaque private key.
///
/// The encoded representation of an `OpaquePrivateKeyBytes` consists of a 4-byte length prefix,
/// followed by its byte representation.
#[derive(Clone)]
pub struct OpaquePrivateKeyBytes(Vec<u8>);
/// An opaque keypair.
///
/// The encoded representation of an `OpaqueKeypair` consists of the encoded representation of its
/// [`OpaquePublicKey`] followed by the encoded representation of its [`OpaquePrivateKeyBytes`].
#[derive(Clone)]
pub struct OpaqueKeypair {
/// The opaque private key
pub private: OpaquePrivateKeyBytes,
/// The opaque public key
pub public: OpaquePublicKey,
}
/// The underlying representation of an [`OpaqueKeypair`].
///
/// The encoded representation of an `OpaqueKeypairBytes` consists of the encoded representation of
/// its [`OpaquePublicKeyBytes`] followed by the encoded representation of its
/// [`OpaquePrivateKeyBytes`].
pub struct OpaqueKeypairBytes {
/// The opaque private key
pub private: OpaquePrivateKeyBytes,
/// The opaque public key
pub public: OpaquePublicKeyBytes,
}
impl OpaqueKeypair {
/// Create a new `OpaqueKeypair`.
pub fn new(private_key: Vec<u8>, public: OpaquePublicKey) -> Self {
Self {
private: OpaquePrivateKeyBytes(private_key),
public,
}
}
/// Get the [`Algorithm`] for this key type.
pub fn algorithm(&self) -> Algorithm {
self.public.algorithm()
}
/// Decode [`OpaqueKeypair`] for the specified algorithm.
pub(super) fn decode_as(reader: &mut impl Reader, algorithm: Algorithm) -> Result<Self> {
let key = OpaqueKeypairBytes::decode(reader)?;
let public = OpaquePublicKey {
algorithm,
key: key.public,
};
Ok(Self {
public,
private: key.private,
})
}
}
impl Decode for OpaquePrivateKeyBytes {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let len = usize::decode(reader)?;
let mut bytes = vec![0; len];
reader.read(&mut bytes)?;
Ok(Self(bytes))
}
}
impl Decode for OpaqueKeypairBytes {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let public = OpaquePublicKeyBytes::decode(reader)?;
let private = OpaquePrivateKeyBytes::decode(reader)?;
Ok(Self { public, private })
}
}
impl Encode for OpaqueKeypair {
fn encoded_len(&self) -> encoding::Result<usize> {
[self.public.encoded_len()?, self.private.encoded_len()?].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.public.encode(writer)?;
self.private.encode(writer)?;
Ok(())
}
}
impl ConstantTimeEq for OpaqueKeypair {
fn ct_eq(&self, other: &Self) -> Choice {
Choice::from((self.public == other.public) as u8) & self.private.ct_eq(&other.private)
}
}
impl Encode for OpaquePrivateKeyBytes {
fn encoded_len(&self) -> encoding::Result<usize> {
self.0.encoded_len()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.0.encode(writer)
}
}
impl From<&OpaqueKeypair> for OpaquePublicKey {
fn from(keypair: &OpaqueKeypair) -> OpaquePublicKey {
keypair.public.clone()
}
}
impl fmt::Debug for OpaqueKeypair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OpaqueKeypair")
.field("public", &self.public)
.finish_non_exhaustive()
}
}
impl ConstantTimeEq for OpaquePrivateKeyBytes {
fn ct_eq(&self, other: &Self) -> Choice {
self.as_ref().ct_eq(other.as_ref())
}
}
impl AsRef<[u8]> for OpaquePrivateKeyBytes {
fn as_ref(&self) -> &[u8] {
&self.0
}
}

260
vendor/ssh-key/src/private/rsa.rs vendored Normal file
View File

@@ -0,0 +1,260 @@
//! RivestShamirAdleman (RSA) private keys.
use crate::{public::RsaPublicKey, Error, Mpint, Result};
use core::fmt;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use subtle::{Choice, ConstantTimeEq};
use zeroize::Zeroize;
#[cfg(feature = "rsa")]
use {
rand_core::CryptoRngCore,
rsa::{
pkcs1v15,
traits::{PrivateKeyParts, PublicKeyParts},
},
sha2::{digest::const_oid::AssociatedOid, Digest},
};
/// RSA private key.
#[derive(Clone)]
pub struct RsaPrivateKey {
/// RSA private exponent.
pub d: Mpint,
/// CRT coefficient: `(inverse of q) mod p`.
pub iqmp: Mpint,
/// First prime factor of `n`.
pub p: Mpint,
/// Second prime factor of `n`.
pub q: Mpint,
}
impl ConstantTimeEq for RsaPrivateKey {
fn ct_eq(&self, other: &Self) -> Choice {
self.d.ct_eq(&other.d)
& self.iqmp.ct_eq(&self.iqmp)
& self.p.ct_eq(&other.p)
& self.q.ct_eq(&other.q)
}
}
impl Eq for RsaPrivateKey {}
impl PartialEq for RsaPrivateKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Decode for RsaPrivateKey {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let d = Mpint::decode(reader)?;
let iqmp = Mpint::decode(reader)?;
let p = Mpint::decode(reader)?;
let q = Mpint::decode(reader)?;
Ok(Self { d, iqmp, p, q })
}
}
impl Encode for RsaPrivateKey {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.d.encoded_len()?,
self.iqmp.encoded_len()?,
self.p.encoded_len()?,
self.q.encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.d.encode(writer)?;
self.iqmp.encode(writer)?;
self.p.encode(writer)?;
self.q.encode(writer)?;
Ok(())
}
}
impl Drop for RsaPrivateKey {
fn drop(&mut self) {
self.d.zeroize();
self.iqmp.zeroize();
self.p.zeroize();
self.q.zeroize();
}
}
/// RSA private/public keypair.
#[derive(Clone)]
pub struct RsaKeypair {
/// Public key.
pub public: RsaPublicKey,
/// Private key.
pub private: RsaPrivateKey,
}
impl RsaKeypair {
/// Minimum allowed RSA key size.
#[cfg(feature = "rsa")]
pub(crate) const MIN_KEY_SIZE: usize = 2048;
/// Generate a random RSA keypair of the given size.
#[cfg(feature = "rsa")]
pub fn random(rng: &mut impl CryptoRngCore, bit_size: usize) -> Result<Self> {
if bit_size >= Self::MIN_KEY_SIZE {
rsa::RsaPrivateKey::new(rng, bit_size)?.try_into()
} else {
Err(Error::Crypto)
}
}
}
impl ConstantTimeEq for RsaKeypair {
fn ct_eq(&self, other: &Self) -> Choice {
Choice::from((self.public == other.public) as u8) & self.private.ct_eq(&other.private)
}
}
impl Eq for RsaKeypair {}
impl PartialEq for RsaKeypair {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Decode for RsaKeypair {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let n = Mpint::decode(reader)?;
let e = Mpint::decode(reader)?;
let public = RsaPublicKey { n, e };
let private = RsaPrivateKey::decode(reader)?;
Ok(RsaKeypair { public, private })
}
}
impl Encode for RsaKeypair {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.public.n.encoded_len()?,
self.public.e.encoded_len()?,
self.private.encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.public.n.encode(writer)?;
self.public.e.encode(writer)?;
self.private.encode(writer)
}
}
impl From<RsaKeypair> for RsaPublicKey {
fn from(keypair: RsaKeypair) -> RsaPublicKey {
keypair.public
}
}
impl From<&RsaKeypair> for RsaPublicKey {
fn from(keypair: &RsaKeypair) -> RsaPublicKey {
keypair.public.clone()
}
}
impl fmt::Debug for RsaKeypair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RsaKeypair")
.field("public", &self.public)
.finish_non_exhaustive()
}
}
#[cfg(feature = "rsa")]
impl TryFrom<RsaKeypair> for rsa::RsaPrivateKey {
type Error = Error;
fn try_from(key: RsaKeypair) -> Result<rsa::RsaPrivateKey> {
rsa::RsaPrivateKey::try_from(&key)
}
}
#[cfg(feature = "rsa")]
impl TryFrom<&RsaKeypair> for rsa::RsaPrivateKey {
type Error = Error;
fn try_from(key: &RsaKeypair) -> Result<rsa::RsaPrivateKey> {
let ret = rsa::RsaPrivateKey::from_components(
rsa::BigUint::try_from(&key.public.n)?,
rsa::BigUint::try_from(&key.public.e)?,
rsa::BigUint::try_from(&key.private.d)?,
vec![
rsa::BigUint::try_from(&key.private.p)?,
rsa::BigUint::try_from(&key.private.p)?,
],
)?;
if ret.size().saturating_mul(8) >= RsaKeypair::MIN_KEY_SIZE {
Ok(ret)
} else {
Err(Error::Crypto)
}
}
}
#[cfg(feature = "rsa")]
impl TryFrom<rsa::RsaPrivateKey> for RsaKeypair {
type Error = Error;
fn try_from(key: rsa::RsaPrivateKey) -> Result<RsaKeypair> {
RsaKeypair::try_from(&key)
}
}
#[cfg(feature = "rsa")]
impl TryFrom<&rsa::RsaPrivateKey> for RsaKeypair {
type Error = Error;
fn try_from(key: &rsa::RsaPrivateKey) -> Result<RsaKeypair> {
// Multi-prime keys are not supported
if key.primes().len() > 2 {
return Err(Error::Crypto);
}
let public = RsaPublicKey::try_from(key.to_public_key())?;
let p = &key.primes()[0];
let q = &key.primes()[1];
let iqmp = key.crt_coefficient().ok_or(Error::Crypto)?;
let private = RsaPrivateKey {
d: key.d().try_into()?,
iqmp: iqmp.try_into()?,
p: p.try_into()?,
q: q.try_into()?,
};
Ok(RsaKeypair { public, private })
}
}
#[cfg(feature = "rsa")]
impl<D> TryFrom<&RsaKeypair> for pkcs1v15::SigningKey<D>
where
D: Digest + AssociatedOid,
{
type Error = Error;
fn try_from(keypair: &RsaKeypair) -> Result<pkcs1v15::SigningKey<D>> {
Ok(pkcs1v15::SigningKey::new(keypair.try_into()?))
}
}

188
vendor/ssh-key/src/private/sk.rs vendored Normal file
View File

@@ -0,0 +1,188 @@
//! Security Key (FIDO/U2F) private keys as described in [PROTOCOL.u2f].
//!
//! [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
use crate::{public, Error, Result};
use alloc::vec::Vec;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
/// Security Key (FIDO/U2F) ECDSA/NIST P-256 private key as specified in
/// [PROTOCOL.u2f](https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD).
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct SkEcdsaSha2NistP256 {
/// Public key.
public: public::SkEcdsaSha2NistP256,
/// Flags.
flags: u8,
/// FIDO/U2F key handle.
key_handle: Vec<u8>,
/// Reserved data.
reserved: Vec<u8>,
}
#[cfg(feature = "ecdsa")]
impl SkEcdsaSha2NistP256 {
/// Construct new instance of SkEcdsaSha2NistP256.
#[cfg(feature = "alloc")]
pub fn new(
public: public::SkEcdsaSha2NistP256,
flags: u8,
key_handle: impl Into<Vec<u8>>,
) -> Result<Self> {
let key_handle = key_handle.into();
if key_handle.len() <= 255 {
Ok(SkEcdsaSha2NistP256 {
public,
flags,
key_handle,
reserved: Vec::<u8>::new(),
})
} else {
Err(encoding::Error::Length.into())
}
}
/// Get the ECDSA/NIST P-256 public key.
pub fn public(&self) -> &public::SkEcdsaSha2NistP256 {
&self.public
}
/// Get flags.
pub fn flags(&self) -> u8 {
self.flags
}
/// Get FIDO/U2F key handle.
pub fn key_handle(&self) -> &[u8] {
&self.key_handle
}
}
#[cfg(feature = "ecdsa")]
impl Decode for SkEcdsaSha2NistP256 {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
Ok(Self {
public: public::SkEcdsaSha2NistP256::decode(reader)?,
flags: u8::decode(reader)?,
key_handle: Vec::decode(reader)?,
reserved: Vec::decode(reader)?,
})
}
}
#[cfg(feature = "ecdsa")]
impl Encode for SkEcdsaSha2NistP256 {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.public.encoded_len()?,
self.flags.encoded_len()?,
self.key_handle.encoded_len()?,
self.reserved.encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.public.encode(writer)?;
self.flags.encode(writer)?;
self.key_handle.encode(writer)?;
self.reserved.encode(writer)?;
Ok(())
}
}
/// Security Key (FIDO/U2F) Ed25519 private key as specified in
/// [PROTOCOL.u2f](https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD).
#[cfg(feature = "alloc")]
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct SkEd25519 {
/// Public key.
public: public::SkEd25519,
/// Flags.
flags: u8,
/// FIDO/U2F key handle.
key_handle: Vec<u8>,
/// Reserved data.
reserved: Vec<u8>,
}
impl SkEd25519 {
/// Construct new instance of SkEd25519.
#[cfg(feature = "alloc")]
pub fn new(
public: public::SkEd25519,
flags: u8,
key_handle: impl Into<Vec<u8>>,
) -> Result<Self> {
let key_handle = key_handle.into();
if key_handle.len() <= 255 {
Ok(SkEd25519 {
public,
flags,
key_handle,
reserved: Vec::<u8>::new(),
})
} else {
Err(encoding::Error::Length.into())
}
}
/// Get the Ed25519 public key.
pub fn public(&self) -> &public::SkEd25519 {
&self.public
}
/// Get flags.
pub fn flags(&self) -> u8 {
self.flags
}
/// Get FIDO/U2F key handle.
pub fn key_handle(&self) -> &[u8] {
&self.key_handle
}
}
impl Decode for SkEd25519 {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
Ok(Self {
public: public::SkEd25519::decode(reader)?,
flags: u8::decode(reader)?,
key_handle: Vec::decode(reader)?,
reserved: Vec::decode(reader)?,
})
}
}
impl Encode for SkEd25519 {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.public.encoded_len()?,
self.flags.encoded_len()?,
self.key_handle.encoded_len()?,
self.reserved.encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.public.encode(writer)?;
self.flags.encode(writer)?;
self.key_handle.encode(writer)?;
self.reserved.encode(writer)?;
Ok(())
}
}

403
vendor/ssh-key/src/public.rs vendored Normal file
View File

@@ -0,0 +1,403 @@
//! SSH public key support.
//!
//! Support for decoding SSH public keys from the OpenSSH file format.
#[cfg(feature = "alloc")]
mod dsa;
#[cfg(feature = "ecdsa")]
mod ecdsa;
mod ed25519;
mod key_data;
#[cfg(feature = "alloc")]
mod opaque;
#[cfg(feature = "alloc")]
mod rsa;
mod sk;
mod ssh_format;
pub use self::{ed25519::Ed25519PublicKey, key_data::KeyData, sk::SkEd25519};
#[cfg(feature = "alloc")]
pub use self::{
dsa::DsaPublicKey,
opaque::{OpaquePublicKey, OpaquePublicKeyBytes},
rsa::RsaPublicKey,
};
#[cfg(feature = "ecdsa")]
pub use self::{ecdsa::EcdsaPublicKey, sk::SkEcdsaSha2NistP256};
pub(crate) use self::ssh_format::SshFormat;
use crate::{Algorithm, Error, Fingerprint, HashAlg, Result};
use core::str::FromStr;
use encoding::{Base64Reader, Decode, Reader};
#[cfg(feature = "alloc")]
use {
crate::SshSig,
alloc::{
borrow::ToOwned,
string::{String, ToString},
vec::Vec,
},
encoding::Encode,
};
#[cfg(all(feature = "alloc", feature = "serde"))]
use serde::{de, ser, Deserialize, Serialize};
#[cfg(feature = "std")]
use std::{fs, path::Path};
#[cfg(doc)]
use crate::PrivateKey;
/// SSH public key.
///
/// # OpenSSH encoding
///
/// The OpenSSH encoding of an SSH public key looks like following:
///
/// ```text
/// ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti user@example.com
/// ```
///
/// It consists of the following three parts:
///
/// 1. Algorithm identifier (in this example `ssh-ed25519`)
/// 2. Key data encoded as Base64
/// 3. Comment (optional): arbitrary label describing a key. Usually an email address
///
/// The [`PublicKey::from_openssh`] and [`PublicKey::to_openssh`] methods can be
/// used to decode/encode public keys, or alternatively, the [`FromStr`] and
/// [`ToString`] impls.
///
/// # `serde` support
///
/// When the `serde` feature of this crate is enabled, this type receives impls
/// of [`Deserialize`][`serde::Deserialize`] and [`Serialize`][`serde::Serialize`].
///
/// The serialization uses a binary encoding with binary formats like bincode
/// and CBOR, and the OpenSSH string serialization when used with
/// human-readable formats like JSON and TOML.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct PublicKey {
/// Key data.
pub(crate) key_data: KeyData,
/// Comment on the key (e.g. email address)
#[cfg(feature = "alloc")]
pub(crate) comment: String,
}
impl PublicKey {
/// Create a new public key with the given comment.
///
/// On `no_std` platforms, use `PublicKey::from(key_data)` instead.
#[cfg(feature = "alloc")]
pub fn new(key_data: KeyData, comment: impl Into<String>) -> Self {
Self {
key_data,
comment: comment.into(),
}
}
/// Parse an OpenSSH-formatted public key.
///
/// OpenSSH-formatted public keys look like the following:
///
/// ```text
/// ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti foo@bar.com
/// ```
pub fn from_openssh(public_key: &str) -> Result<Self> {
let encapsulation = SshFormat::decode(public_key.trim_end().as_bytes())?;
let mut reader = Base64Reader::new(encapsulation.base64_data)?;
let key_data = KeyData::decode(&mut reader)?;
// Verify that the algorithm in the Base64-encoded data matches the text
if encapsulation.algorithm_id != key_data.algorithm().as_str() {
return Err(Error::AlgorithmUnknown);
}
let public_key = Self {
key_data,
#[cfg(feature = "alloc")]
comment: encapsulation.comment.to_owned(),
};
Ok(reader.finish(public_key)?)
}
/// Parse a raw binary SSH public key.
pub fn from_bytes(mut bytes: &[u8]) -> Result<Self> {
let reader = &mut bytes;
let key_data = KeyData::decode(reader)?;
Ok(reader.finish(key_data.into())?)
}
/// Encode OpenSSH-formatted public key.
pub fn encode_openssh<'o>(&self, out: &'o mut [u8]) -> Result<&'o str> {
SshFormat::encode(
self.algorithm().as_str(),
&self.key_data,
self.comment(),
out,
)
}
/// Encode an OpenSSH-formatted public key, allocating a [`String`] for
/// the result.
#[cfg(feature = "alloc")]
pub fn to_openssh(&self) -> Result<String> {
SshFormat::encode_string(self.algorithm().as_str(), &self.key_data, self.comment())
}
/// Serialize SSH public key as raw bytes.
#[cfg(feature = "alloc")]
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let mut public_key_bytes = Vec::new();
self.key_data.encode(&mut public_key_bytes)?;
Ok(public_key_bytes)
}
/// Verify the [`SshSig`] signature over the given message using this
/// public key.
///
/// These signatures can be produced using `ssh-keygen -Y sign`. They're
/// encoded as PEM and begin with the following:
///
/// ```text
/// -----BEGIN SSH SIGNATURE-----
/// ```
///
/// See [PROTOCOL.sshsig] for more information.
///
/// # Usage
///
/// See also: [`PrivateKey::sign`].
///
#[cfg_attr(feature = "ed25519", doc = "```")]
#[cfg_attr(not(feature = "ed25519"), doc = "```ignore")]
/// # fn main() -> Result<(), ssh_key::Error> {
/// use ssh_key::{PublicKey, SshSig};
///
/// // Message to be verified.
/// let message = b"testing";
///
/// // Example domain/namespace used for the message.
/// let namespace = "example";
///
/// // Public key which computed the signature.
/// let encoded_public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti user@example.com";
///
/// // Example signature to be verified.
/// let signature_str = r#"
/// -----BEGIN SSH SIGNATURE-----
/// U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgsz6u836i33yqAQ3v3qNOJB9l8b
/// UppPQ+0UMn9cVKq2IAAAAHZXhhbXBsZQAAAAAAAAAGc2hhNTEyAAAAUwAAAAtzc2gtZWQy
/// NTUxOQAAAEBPEav+tMGNnox4MuzM7rlHyVBajCn8B0kAyiOWwPKprNsG3i6X+voz/WCSik
/// /FowYwqhgCABUJSvRX3AERVBUP
/// -----END SSH SIGNATURE-----
/// "#;
///
/// let public_key = encoded_public_key.parse::<PublicKey>()?;
/// let signature = signature_str.parse::<SshSig>()?;
/// public_key.verify(namespace, message, &signature)?;
/// # Ok(())
/// # }
/// ```
///
/// [PROTOCOL.sshsig]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.sshsig?annotate=HEAD
#[cfg(feature = "alloc")]
pub fn verify(&self, namespace: &str, msg: &[u8], signature: &SshSig) -> Result<()> {
if self.key_data() != signature.public_key() {
return Err(Error::PublicKey);
}
if namespace != signature.namespace() {
return Err(Error::Namespace);
}
signature.verify(msg)
}
/// Read public key from an OpenSSH-formatted file.
#[cfg(feature = "std")]
pub fn read_openssh_file(path: &Path) -> Result<Self> {
let input = fs::read_to_string(path)?;
Self::from_openssh(&input)
}
/// Write public key as an OpenSSH-formatted file.
#[cfg(feature = "std")]
pub fn write_openssh_file(&self, path: &Path) -> Result<()> {
let mut encoded = self.to_openssh()?;
encoded.push('\n'); // TODO(tarcieri): OS-specific line endings?
fs::write(path, encoded.as_bytes())?;
Ok(())
}
/// Get the digital signature [`Algorithm`] used by this key.
pub fn algorithm(&self) -> Algorithm {
self.key_data.algorithm()
}
/// Comment on the key (e.g. email address).
#[cfg(not(feature = "alloc"))]
pub fn comment(&self) -> &str {
""
}
/// Comment on the key (e.g. email address).
#[cfg(feature = "alloc")]
pub fn comment(&self) -> &str {
&self.comment
}
/// Public key data.
pub fn key_data(&self) -> &KeyData {
&self.key_data
}
/// Compute key fingerprint.
///
/// Use [`Default::default()`] to use the default hash function (SHA-256).
pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint {
self.key_data.fingerprint(hash_alg)
}
/// Set the comment on the key.
#[cfg(feature = "alloc")]
pub fn set_comment(&mut self, comment: impl Into<String>) {
self.comment = comment.into();
}
/// Decode comment (e.g. email address).
///
/// This is a stub implementation that ignores the comment.
#[cfg(not(feature = "alloc"))]
pub(crate) fn decode_comment(&mut self, reader: &mut impl Reader) -> Result<()> {
reader.drain_prefixed()?;
Ok(())
}
/// Decode comment (e.g. email address)
#[cfg(feature = "alloc")]
pub(crate) fn decode_comment(&mut self, reader: &mut impl Reader) -> Result<()> {
self.comment = String::decode(reader)?;
Ok(())
}
}
impl From<KeyData> for PublicKey {
fn from(key_data: KeyData) -> PublicKey {
PublicKey {
key_data,
#[cfg(feature = "alloc")]
comment: String::new(),
}
}
}
impl From<PublicKey> for KeyData {
fn from(public_key: PublicKey) -> KeyData {
public_key.key_data
}
}
impl From<&PublicKey> for KeyData {
fn from(public_key: &PublicKey) -> KeyData {
public_key.key_data.clone()
}
}
#[cfg(feature = "alloc")]
impl From<DsaPublicKey> for PublicKey {
fn from(public_key: DsaPublicKey) -> PublicKey {
KeyData::from(public_key).into()
}
}
#[cfg(feature = "ecdsa")]
impl From<EcdsaPublicKey> for PublicKey {
fn from(public_key: EcdsaPublicKey) -> PublicKey {
KeyData::from(public_key).into()
}
}
impl From<Ed25519PublicKey> for PublicKey {
fn from(public_key: Ed25519PublicKey) -> PublicKey {
KeyData::from(public_key).into()
}
}
#[cfg(feature = "alloc")]
impl From<RsaPublicKey> for PublicKey {
fn from(public_key: RsaPublicKey) -> PublicKey {
KeyData::from(public_key).into()
}
}
#[cfg(feature = "ecdsa")]
impl From<SkEcdsaSha2NistP256> for PublicKey {
fn from(public_key: SkEcdsaSha2NistP256) -> PublicKey {
KeyData::from(public_key).into()
}
}
impl From<SkEd25519> for PublicKey {
fn from(public_key: SkEd25519) -> PublicKey {
KeyData::from(public_key).into()
}
}
impl FromStr for PublicKey {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::from_openssh(s)
}
}
#[cfg(feature = "alloc")]
impl ToString for PublicKey {
fn to_string(&self) -> String {
self.to_openssh().expect("SSH public key encoding error")
}
}
#[cfg(all(feature = "alloc", feature = "serde"))]
impl<'de> Deserialize<'de> for PublicKey {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
if deserializer.is_human_readable() {
let string = String::deserialize(deserializer)?;
Self::from_openssh(&string).map_err(de::Error::custom)
} else {
let bytes = Vec::<u8>::deserialize(deserializer)?;
Self::from_bytes(&bytes).map_err(de::Error::custom)
}
}
}
#[cfg(all(feature = "alloc", feature = "serde"))]
impl Serialize for PublicKey {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
if serializer.is_human_readable() {
self.to_openssh()
.map_err(ser::Error::custom)?
.serialize(serializer)
} else {
self.to_bytes()
.map_err(ser::Error::custom)?
.serialize(serializer)
}
}
}

113
vendor/ssh-key/src/public/dsa.rs vendored Normal file
View File

@@ -0,0 +1,113 @@
//! Digital Signature Algorithm (DSA) public keys.
use crate::{Error, Mpint, Result};
use core::hash::{Hash, Hasher};
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
/// Digital Signature Algorithm (DSA) public key.
///
/// Described in [FIPS 186-4 § 4.1](https://csrc.nist.gov/publications/detail/fips/186/4/final).
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct DsaPublicKey {
/// Prime modulus.
pub p: Mpint,
/// Prime divisor of `p - 1`.
pub q: Mpint,
/// Generator of a subgroup of order `q` in the multiplicative group
/// `GF(p)`, such that `1 < g < p`.
pub g: Mpint,
/// The public key, where `y = gˣ mod p`.
pub y: Mpint,
}
impl Decode for DsaPublicKey {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let p = Mpint::decode(reader)?;
let q = Mpint::decode(reader)?;
let g = Mpint::decode(reader)?;
let y = Mpint::decode(reader)?;
Ok(Self { p, q, g, y })
}
}
impl Encode for DsaPublicKey {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.p.encoded_len()?,
self.q.encoded_len()?,
self.g.encoded_len()?,
self.y.encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.p.encode(writer)?;
self.q.encode(writer)?;
self.g.encode(writer)?;
self.y.encode(writer)
}
}
impl Hash for DsaPublicKey {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.p.as_bytes().hash(state);
self.q.as_bytes().hash(state);
self.g.as_bytes().hash(state);
self.y.as_bytes().hash(state);
}
}
#[cfg(feature = "dsa")]
impl TryFrom<DsaPublicKey> for dsa::VerifyingKey {
type Error = Error;
fn try_from(key: DsaPublicKey) -> Result<dsa::VerifyingKey> {
dsa::VerifyingKey::try_from(&key)
}
}
#[cfg(feature = "dsa")]
impl TryFrom<&DsaPublicKey> for dsa::VerifyingKey {
type Error = Error;
fn try_from(key: &DsaPublicKey) -> Result<dsa::VerifyingKey> {
let components = dsa::Components::from_components(
dsa::BigUint::try_from(&key.p)?,
dsa::BigUint::try_from(&key.q)?,
dsa::BigUint::try_from(&key.g)?,
)?;
dsa::VerifyingKey::from_components(components, dsa::BigUint::try_from(&key.y)?)
.map_err(|_| Error::Crypto)
}
}
#[cfg(feature = "dsa")]
impl TryFrom<dsa::VerifyingKey> for DsaPublicKey {
type Error = Error;
fn try_from(key: dsa::VerifyingKey) -> Result<DsaPublicKey> {
DsaPublicKey::try_from(&key)
}
}
#[cfg(feature = "dsa")]
impl TryFrom<&dsa::VerifyingKey> for DsaPublicKey {
type Error = Error;
fn try_from(key: &dsa::VerifyingKey) -> Result<DsaPublicKey> {
Ok(DsaPublicKey {
p: key.components().p().try_into()?,
q: key.components().q().try_into()?,
g: key.components().g().try_into()?,
y: key.y().try_into()?,
})
}
}

202
vendor/ssh-key/src/public/ecdsa.rs vendored Normal file
View File

@@ -0,0 +1,202 @@
//! Elliptic Curve Digital Signature Algorithm (ECDSA) public keys.
use crate::{Algorithm, EcdsaCurve, Error, Result};
use core::fmt;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use sec1::consts::{U32, U48, U66};
/// ECDSA/NIST P-256 public key.
pub type EcdsaNistP256PublicKey = sec1::EncodedPoint<U32>;
/// ECDSA/NIST P-384 public key.
pub type EcdsaNistP384PublicKey = sec1::EncodedPoint<U48>;
/// ECDSA/NIST P-521 public key.
pub type EcdsaNistP521PublicKey = sec1::EncodedPoint<U66>;
/// Elliptic Curve Digital Signature Algorithm (ECDSA) public key.
///
/// Public keys are represented as [`sec1::EncodedPoint`] and require the
/// `sec1` feature of this crate is enabled (which it is by default).
///
/// Described in [FIPS 186-4](https://csrc.nist.gov/publications/detail/fips/186/4/final).
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum EcdsaPublicKey {
/// NIST P-256 ECDSA public key.
NistP256(EcdsaNistP256PublicKey),
/// NIST P-384 ECDSA public key.
NistP384(EcdsaNistP384PublicKey),
/// NIST P-521 ECDSA public key.
NistP521(EcdsaNistP521PublicKey),
}
impl EcdsaPublicKey {
/// Maximum size of a SEC1-encoded ECDSA public key (i.e. curve point).
///
/// This is the size of 2 * P-521 field elements (2 * 66 = 132) which
/// represent the affine coordinates of a curve point plus one additional
/// byte for the SEC1 "tag" identifying the curve point encoding.
const MAX_SIZE: usize = 133;
/// Parse an ECDSA public key from a SEC1-encoded point.
///
/// Determines the key type from the SEC1 tag byte and length.
pub fn from_sec1_bytes(bytes: &[u8]) -> Result<Self> {
match bytes {
[tag, rest @ ..] => {
let point_size = match sec1::point::Tag::from_u8(*tag)? {
sec1::point::Tag::CompressedEvenY | sec1::point::Tag::CompressedOddY => {
rest.len()
}
sec1::point::Tag::Uncompressed => rest.len() / 2,
_ => return Err(Error::AlgorithmUnknown),
};
match point_size {
32 => Ok(Self::NistP256(EcdsaNistP256PublicKey::from_bytes(bytes)?)),
48 => Ok(Self::NistP384(EcdsaNistP384PublicKey::from_bytes(bytes)?)),
66 => Ok(Self::NistP521(EcdsaNistP521PublicKey::from_bytes(bytes)?)),
_ => Err(encoding::Error::Length.into()),
}
}
_ => Err(encoding::Error::Length.into()),
}
}
/// Borrow the SEC1-encoded key data as bytes.
pub fn as_sec1_bytes(&self) -> &[u8] {
match self {
EcdsaPublicKey::NistP256(point) => point.as_bytes(),
EcdsaPublicKey::NistP384(point) => point.as_bytes(),
EcdsaPublicKey::NistP521(point) => point.as_bytes(),
}
}
/// Get the [`Algorithm`] for this public key type.
pub fn algorithm(&self) -> Algorithm {
Algorithm::Ecdsa {
curve: self.curve(),
}
}
/// Get the [`EcdsaCurve`] for this key.
pub fn curve(&self) -> EcdsaCurve {
match self {
EcdsaPublicKey::NistP256(_) => EcdsaCurve::NistP256,
EcdsaPublicKey::NistP384(_) => EcdsaCurve::NistP384,
EcdsaPublicKey::NistP521(_) => EcdsaCurve::NistP521,
}
}
}
impl AsRef<[u8]> for EcdsaPublicKey {
fn as_ref(&self) -> &[u8] {
self.as_sec1_bytes()
}
}
impl Decode for EcdsaPublicKey {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let curve = EcdsaCurve::decode(reader)?;
let mut buf = [0u8; Self::MAX_SIZE];
let key = Self::from_sec1_bytes(reader.read_byten(&mut buf)?)?;
if key.curve() == curve {
Ok(key)
} else {
Err(Error::AlgorithmUnknown)
}
}
}
impl Encode for EcdsaPublicKey {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.curve().encoded_len()?,
4, // uint32 length prefix
self.as_ref().len(),
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.curve().encode(writer)?;
self.as_ref().encode(writer)?;
Ok(())
}
}
impl fmt::Display for EcdsaPublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:X}")
}
}
impl fmt::LowerHex for EcdsaPublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_sec1_bytes() {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
impl fmt::UpperHex for EcdsaPublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_sec1_bytes() {
write!(f, "{byte:02X}")?;
}
Ok(())
}
}
macro_rules! impl_ecdsa_for_curve {
($krate:ident, $feature:expr, $curve:ident) => {
#[cfg(feature = $feature)]
impl TryFrom<EcdsaPublicKey> for $krate::ecdsa::VerifyingKey {
type Error = Error;
fn try_from(key: EcdsaPublicKey) -> Result<$krate::ecdsa::VerifyingKey> {
$krate::ecdsa::VerifyingKey::try_from(&key)
}
}
#[cfg(feature = $feature)]
impl TryFrom<&EcdsaPublicKey> for $krate::ecdsa::VerifyingKey {
type Error = Error;
fn try_from(public_key: &EcdsaPublicKey) -> Result<$krate::ecdsa::VerifyingKey> {
match public_key {
EcdsaPublicKey::$curve(key) => {
$krate::ecdsa::VerifyingKey::from_encoded_point(key)
.map_err(|_| Error::Crypto)
}
_ => Err(Error::AlgorithmUnknown),
}
}
}
#[cfg(feature = $feature)]
impl From<$krate::ecdsa::VerifyingKey> for EcdsaPublicKey {
fn from(key: $krate::ecdsa::VerifyingKey) -> EcdsaPublicKey {
EcdsaPublicKey::from(&key)
}
}
#[cfg(feature = $feature)]
impl From<&$krate::ecdsa::VerifyingKey> for EcdsaPublicKey {
fn from(key: &$krate::ecdsa::VerifyingKey) -> EcdsaPublicKey {
EcdsaPublicKey::$curve(key.to_encoded_point(false))
}
}
};
}
impl_ecdsa_for_curve!(p256, "p256", NistP256);
impl_ecdsa_for_curve!(p384, "p384", NistP384);
impl_ecdsa_for_curve!(p521, "p521", NistP521);

108
vendor/ssh-key/src/public/ed25519.rs vendored Normal file
View File

@@ -0,0 +1,108 @@
//! Ed25519 public keys.
//!
//! Edwards Digital Signature Algorithm (EdDSA) over Curve25519.
use crate::{Error, Result};
use core::fmt;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
/// Ed25519 public key.
// TODO(tarcieri): use `ed25519::PublicKey`? (doesn't exist yet)
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Ed25519PublicKey(pub [u8; Self::BYTE_SIZE]);
impl Ed25519PublicKey {
/// Size of an Ed25519 public key in bytes.
pub const BYTE_SIZE: usize = 32;
}
impl AsRef<[u8; Self::BYTE_SIZE]> for Ed25519PublicKey {
fn as_ref(&self) -> &[u8; Self::BYTE_SIZE] {
&self.0
}
}
impl Decode for Ed25519PublicKey {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let mut bytes = [0u8; Self::BYTE_SIZE];
reader.read_prefixed(|reader| reader.read(&mut bytes))?;
Ok(Self(bytes))
}
}
impl Encode for Ed25519PublicKey {
fn encoded_len(&self) -> encoding::Result<usize> {
[4, Self::BYTE_SIZE].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.0.encode(writer)?;
Ok(())
}
}
impl TryFrom<&[u8]> for Ed25519PublicKey {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Self> {
Ok(Self(bytes.try_into()?))
}
}
impl fmt::Display for Ed25519PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:X}")
}
}
impl fmt::LowerHex for Ed25519PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_ref() {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
impl fmt::UpperHex for Ed25519PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_ref() {
write!(f, "{byte:02X}")?;
}
Ok(())
}
}
#[cfg(feature = "ed25519")]
impl TryFrom<Ed25519PublicKey> for ed25519_dalek::VerifyingKey {
type Error = Error;
fn try_from(key: Ed25519PublicKey) -> Result<ed25519_dalek::VerifyingKey> {
ed25519_dalek::VerifyingKey::try_from(&key)
}
}
#[cfg(feature = "ed25519")]
impl TryFrom<&Ed25519PublicKey> for ed25519_dalek::VerifyingKey {
type Error = Error;
fn try_from(key: &Ed25519PublicKey) -> Result<ed25519_dalek::VerifyingKey> {
ed25519_dalek::VerifyingKey::from_bytes(key.as_ref()).map_err(|_| Error::Crypto)
}
}
#[cfg(feature = "ed25519")]
impl From<ed25519_dalek::VerifyingKey> for Ed25519PublicKey {
fn from(key: ed25519_dalek::VerifyingKey) -> Ed25519PublicKey {
Ed25519PublicKey::from(&key)
}
}
#[cfg(feature = "ed25519")]
impl From<&ed25519_dalek::VerifyingKey> for Ed25519PublicKey {
fn from(key: &ed25519_dalek::VerifyingKey) -> Ed25519PublicKey {
Ed25519PublicKey(key.to_bytes())
}
}

301
vendor/ssh-key/src/public/key_data.rs vendored Normal file
View File

@@ -0,0 +1,301 @@
//! Public key data.
use super::{Ed25519PublicKey, SkEd25519};
use crate::{Algorithm, Error, Fingerprint, HashAlg, Result};
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
#[cfg(feature = "alloc")]
use super::{DsaPublicKey, OpaquePublicKey, RsaPublicKey};
#[cfg(feature = "ecdsa")]
use super::{EcdsaPublicKey, SkEcdsaSha2NistP256};
/// Public key data.
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum KeyData {
/// Digital Signature Algorithm (DSA) public key data.
#[cfg(feature = "alloc")]
Dsa(DsaPublicKey),
/// Elliptic Curve Digital Signature Algorithm (ECDSA) public key data.
#[cfg(feature = "ecdsa")]
Ecdsa(EcdsaPublicKey),
/// Ed25519 public key data.
Ed25519(Ed25519PublicKey),
/// RSA public key data.
#[cfg(feature = "alloc")]
Rsa(RsaPublicKey),
/// Security Key (FIDO/U2F) using ECDSA/NIST P-256 as specified in [PROTOCOL.u2f].
///
/// [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
#[cfg(feature = "ecdsa")]
SkEcdsaSha2NistP256(SkEcdsaSha2NistP256),
/// Security Key (FIDO/U2F) using Ed25519 as specified in [PROTOCOL.u2f].
///
/// [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
SkEd25519(SkEd25519),
/// Opaque public key data.
#[cfg(feature = "alloc")]
Other(OpaquePublicKey),
}
impl KeyData {
/// Get the [`Algorithm`] for this public key.
pub fn algorithm(&self) -> Algorithm {
match self {
#[cfg(feature = "alloc")]
Self::Dsa(_) => Algorithm::Dsa,
#[cfg(feature = "ecdsa")]
Self::Ecdsa(key) => key.algorithm(),
Self::Ed25519(_) => Algorithm::Ed25519,
#[cfg(feature = "alloc")]
Self::Rsa(_) => Algorithm::Rsa { hash: None },
#[cfg(feature = "ecdsa")]
Self::SkEcdsaSha2NistP256(_) => Algorithm::SkEcdsaSha2NistP256,
Self::SkEd25519(_) => Algorithm::SkEd25519,
#[cfg(feature = "alloc")]
Self::Other(key) => key.algorithm(),
}
}
/// Get DSA public key if this key is the correct type.
#[cfg(feature = "alloc")]
pub fn dsa(&self) -> Option<&DsaPublicKey> {
match self {
Self::Dsa(key) => Some(key),
_ => None,
}
}
/// Get ECDSA public key if this key is the correct type.
#[cfg(feature = "ecdsa")]
pub fn ecdsa(&self) -> Option<&EcdsaPublicKey> {
match self {
Self::Ecdsa(key) => Some(key),
_ => None,
}
}
/// Get Ed25519 public key if this key is the correct type.
pub fn ed25519(&self) -> Option<&Ed25519PublicKey> {
match self {
Self::Ed25519(key) => Some(key),
#[allow(unreachable_patterns)]
_ => None,
}
}
/// Compute key fingerprint.
///
/// Use [`Default::default()`] to use the default hash function (SHA-256).
pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint {
Fingerprint::new(hash_alg, self)
}
/// Get RSA public key if this key is the correct type.
#[cfg(feature = "alloc")]
pub fn rsa(&self) -> Option<&RsaPublicKey> {
match self {
Self::Rsa(key) => Some(key),
_ => None,
}
}
/// Get FIDO/U2F ECDSA/NIST P-256 public key if this key is the correct type.
#[cfg(feature = "ecdsa")]
pub fn sk_ecdsa_p256(&self) -> Option<&SkEcdsaSha2NistP256> {
match self {
Self::SkEcdsaSha2NistP256(sk) => Some(sk),
_ => None,
}
}
/// Get FIDO/U2F Ed25519 public key if this key is the correct type.
pub fn sk_ed25519(&self) -> Option<&SkEd25519> {
match self {
Self::SkEd25519(sk) => Some(sk),
_ => None,
}
}
/// Get the custom, opaque public key if this key is the correct type.
#[cfg(feature = "alloc")]
pub fn other(&self) -> Option<&OpaquePublicKey> {
match self {
Self::Other(key) => Some(key),
_ => None,
}
}
/// Is this key a DSA key?
#[cfg(feature = "alloc")]
pub fn is_dsa(&self) -> bool {
matches!(self, Self::Dsa(_))
}
/// Is this key an ECDSA key?
#[cfg(feature = "ecdsa")]
pub fn is_ecdsa(&self) -> bool {
matches!(self, Self::Ecdsa(_))
}
/// Is this key an Ed25519 key?
pub fn is_ed25519(&self) -> bool {
matches!(self, Self::Ed25519(_))
}
/// Is this key an RSA key?
#[cfg(feature = "alloc")]
pub fn is_rsa(&self) -> bool {
matches!(self, Self::Rsa(_))
}
/// Is this key a FIDO/U2F ECDSA/NIST P-256 key?
#[cfg(feature = "ecdsa")]
pub fn is_sk_ecdsa_p256(&self) -> bool {
matches!(self, Self::SkEcdsaSha2NistP256(_))
}
/// Is this key a FIDO/U2F Ed25519 key?
pub fn is_sk_ed25519(&self) -> bool {
matches!(self, Self::SkEd25519(_))
}
/// Is this a key with a custom algorithm?
#[cfg(feature = "alloc")]
pub fn is_other(&self) -> bool {
matches!(self, Self::Other(_))
}
/// Decode [`KeyData`] for the specified algorithm.
pub(crate) fn decode_as(reader: &mut impl Reader, algorithm: Algorithm) -> Result<Self> {
match algorithm {
#[cfg(feature = "alloc")]
Algorithm::Dsa => DsaPublicKey::decode(reader).map(Self::Dsa),
#[cfg(feature = "ecdsa")]
Algorithm::Ecdsa { curve } => match EcdsaPublicKey::decode(reader)? {
key if key.curve() == curve => Ok(Self::Ecdsa(key)),
_ => Err(Error::AlgorithmUnknown),
},
Algorithm::Ed25519 => Ed25519PublicKey::decode(reader).map(Self::Ed25519),
#[cfg(feature = "alloc")]
Algorithm::Rsa { .. } => RsaPublicKey::decode(reader).map(Self::Rsa),
#[cfg(feature = "ecdsa")]
Algorithm::SkEcdsaSha2NistP256 => {
SkEcdsaSha2NistP256::decode(reader).map(Self::SkEcdsaSha2NistP256)
}
Algorithm::SkEd25519 => SkEd25519::decode(reader).map(Self::SkEd25519),
#[cfg(feature = "alloc")]
Algorithm::Other(_) => OpaquePublicKey::decode_as(reader, algorithm).map(Self::Other),
#[allow(unreachable_patterns)]
_ => Err(Error::AlgorithmUnknown),
}
}
/// Get the encoded length of this key data without a leading algorithm
/// identifier.
pub(crate) fn encoded_key_data_len(&self) -> encoding::Result<usize> {
match self {
#[cfg(feature = "alloc")]
Self::Dsa(key) => key.encoded_len(),
#[cfg(feature = "ecdsa")]
Self::Ecdsa(key) => key.encoded_len(),
Self::Ed25519(key) => key.encoded_len(),
#[cfg(feature = "alloc")]
Self::Rsa(key) => key.encoded_len(),
#[cfg(feature = "ecdsa")]
Self::SkEcdsaSha2NistP256(sk) => sk.encoded_len(),
Self::SkEd25519(sk) => sk.encoded_len(),
#[cfg(feature = "alloc")]
Self::Other(other) => other.key.encoded_len(),
}
}
/// Encode the key data without a leading algorithm identifier.
pub(crate) fn encode_key_data(&self, writer: &mut impl Writer) -> encoding::Result<()> {
match self {
#[cfg(feature = "alloc")]
Self::Dsa(key) => key.encode(writer),
#[cfg(feature = "ecdsa")]
Self::Ecdsa(key) => key.encode(writer),
Self::Ed25519(key) => key.encode(writer),
#[cfg(feature = "alloc")]
Self::Rsa(key) => key.encode(writer),
#[cfg(feature = "ecdsa")]
Self::SkEcdsaSha2NistP256(sk) => sk.encode(writer),
Self::SkEd25519(sk) => sk.encode(writer),
#[cfg(feature = "alloc")]
Self::Other(other) => other.key.encode(writer),
}
}
}
impl Decode for KeyData {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let algorithm = Algorithm::decode(reader)?;
Self::decode_as(reader, algorithm)
}
}
impl Encode for KeyData {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.algorithm().encoded_len()?,
self.encoded_key_data_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.algorithm().encode(writer)?;
self.encode_key_data(writer)
}
}
#[cfg(feature = "alloc")]
impl From<DsaPublicKey> for KeyData {
fn from(public_key: DsaPublicKey) -> KeyData {
Self::Dsa(public_key)
}
}
#[cfg(feature = "ecdsa")]
impl From<EcdsaPublicKey> for KeyData {
fn from(public_key: EcdsaPublicKey) -> KeyData {
Self::Ecdsa(public_key)
}
}
impl From<Ed25519PublicKey> for KeyData {
fn from(public_key: Ed25519PublicKey) -> KeyData {
Self::Ed25519(public_key)
}
}
#[cfg(feature = "alloc")]
impl From<RsaPublicKey> for KeyData {
fn from(public_key: RsaPublicKey) -> KeyData {
Self::Rsa(public_key)
}
}
#[cfg(feature = "ecdsa")]
impl From<SkEcdsaSha2NistP256> for KeyData {
fn from(public_key: SkEcdsaSha2NistP256) -> KeyData {
Self::SkEcdsaSha2NistP256(public_key)
}
}
impl From<SkEd25519> for KeyData {
fn from(public_key: SkEd25519) -> KeyData {
Self::SkEd25519(public_key)
}
}

98
vendor/ssh-key/src/public/opaque.rs vendored Normal file
View File

@@ -0,0 +1,98 @@
//! Opaque public keys.
//!
//! [`OpaquePublicKey`] represents a public key meant to be used with an algorithm unknown to this
//! crate, i.e. public keys that use a custom algorithm as specified in [RFC4251 § 6].
//!
//! They are said to be opaque, because the meaning of their underlying byte representation is not
//! specified.
//!
//! [RFC4251 § 6]: https://www.rfc-editor.org/rfc/rfc4251.html#section-6
use crate::{Algorithm, Error, Result};
use alloc::vec::Vec;
use encoding::{Decode, Encode, Reader, Writer};
/// An opaque public key with a custom algorithm name.
///
/// The encoded representation of an `OpaquePublicKey` is the encoded representation of its
/// [`OpaquePublicKeyBytes`].
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct OpaquePublicKey {
/// The [`Algorithm`] of this public key.
pub algorithm: Algorithm,
/// The key data
pub key: OpaquePublicKeyBytes,
}
/// The underlying representation of an [`OpaquePublicKey`].
///
/// The encoded representation of an `OpaquePublicKeyBytes` consists of a 4-byte length prefix,
/// followed by its byte representation.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct OpaquePublicKeyBytes(Vec<u8>);
impl OpaquePublicKey {
/// Create a new `OpaquePublicKey`.
pub fn new(key: Vec<u8>, algorithm: Algorithm) -> Self {
Self {
key: OpaquePublicKeyBytes(key),
algorithm,
}
}
/// Get the [`Algorithm`] for this public key type.
pub fn algorithm(&self) -> Algorithm {
self.algorithm.clone()
}
/// Decode [`OpaquePublicKey`] for the specified algorithm.
pub(super) fn decode_as(reader: &mut impl Reader, algorithm: Algorithm) -> Result<Self> {
Ok(Self {
algorithm,
key: OpaquePublicKeyBytes::decode(reader)?,
})
}
}
impl Decode for OpaquePublicKeyBytes {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let len = usize::decode(reader)?;
let mut bytes = vec![0; len];
reader.read(&mut bytes)?;
Ok(Self(bytes))
}
}
impl Encode for OpaquePublicKeyBytes {
fn encoded_len(&self) -> encoding::Result<usize> {
self.0.encoded_len()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.0.encode(writer)
}
}
impl Encode for OpaquePublicKey {
fn encoded_len(&self) -> encoding::Result<usize> {
self.key.encoded_len()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.key.encode(writer)
}
}
impl AsRef<[u8]> for OpaquePublicKey {
fn as_ref(&self) -> &[u8] {
self.key.as_ref()
}
}
impl AsRef<[u8]> for OpaquePublicKeyBytes {
fn as_ref(&self) -> &[u8] {
&self.0
}
}

120
vendor/ssh-key/src/public/rsa.rs vendored Normal file
View File

@@ -0,0 +1,120 @@
//! RivestShamirAdleman (RSA) public keys.
use crate::{Error, Mpint, Result};
use core::hash::{Hash, Hasher};
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
#[cfg(feature = "rsa")]
use {
crate::private::RsaKeypair,
rsa::{pkcs1v15, traits::PublicKeyParts},
sha2::{digest::const_oid::AssociatedOid, Digest},
};
/// RSA public key.
///
/// Described in [RFC4253 § 6.6](https://datatracker.ietf.org/doc/html/rfc4253#section-6.6).
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct RsaPublicKey {
/// RSA public exponent.
pub e: Mpint,
/// RSA modulus.
pub n: Mpint,
}
impl RsaPublicKey {
/// Minimum allowed RSA key size.
#[cfg(feature = "rsa")]
pub(crate) const MIN_KEY_SIZE: usize = RsaKeypair::MIN_KEY_SIZE;
}
impl Decode for RsaPublicKey {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let e = Mpint::decode(reader)?;
let n = Mpint::decode(reader)?;
Ok(Self { e, n })
}
}
impl Encode for RsaPublicKey {
fn encoded_len(&self) -> encoding::Result<usize> {
[self.e.encoded_len()?, self.n.encoded_len()?].checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.e.encode(writer)?;
self.n.encode(writer)
}
}
impl Hash for RsaPublicKey {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.e.as_bytes().hash(state);
self.n.as_bytes().hash(state);
}
}
#[cfg(feature = "rsa")]
impl TryFrom<RsaPublicKey> for rsa::RsaPublicKey {
type Error = Error;
fn try_from(key: RsaPublicKey) -> Result<rsa::RsaPublicKey> {
rsa::RsaPublicKey::try_from(&key)
}
}
#[cfg(feature = "rsa")]
impl TryFrom<&RsaPublicKey> for rsa::RsaPublicKey {
type Error = Error;
fn try_from(key: &RsaPublicKey) -> Result<rsa::RsaPublicKey> {
let ret = rsa::RsaPublicKey::new(
rsa::BigUint::try_from(&key.n)?,
rsa::BigUint::try_from(&key.e)?,
)
.map_err(|_| Error::Crypto)?;
if ret.size().saturating_mul(8) >= RsaPublicKey::MIN_KEY_SIZE {
Ok(ret)
} else {
Err(Error::Crypto)
}
}
}
#[cfg(feature = "rsa")]
impl TryFrom<rsa::RsaPublicKey> for RsaPublicKey {
type Error = Error;
fn try_from(key: rsa::RsaPublicKey) -> Result<RsaPublicKey> {
RsaPublicKey::try_from(&key)
}
}
#[cfg(feature = "rsa")]
impl TryFrom<&rsa::RsaPublicKey> for RsaPublicKey {
type Error = Error;
fn try_from(key: &rsa::RsaPublicKey) -> Result<RsaPublicKey> {
Ok(RsaPublicKey {
e: key.e().try_into()?,
n: key.n().try_into()?,
})
}
}
#[cfg(feature = "rsa")]
impl<D> TryFrom<&RsaPublicKey> for pkcs1v15::VerifyingKey<D>
where
D: Digest + AssociatedOid,
{
type Error = Error;
fn try_from(key: &RsaPublicKey) -> Result<pkcs1v15::VerifyingKey<D>> {
Ok(pkcs1v15::VerifyingKey::new(key.try_into()?))
}
}

211
vendor/ssh-key/src/public/sk.rs vendored Normal file
View File

@@ -0,0 +1,211 @@
//! Security Key (FIDO/U2F) public keys as described in [PROTOCOL.u2f].
//!
//! [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
use super::Ed25519PublicKey;
use crate::{Error, Result};
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
#[cfg(feature = "alloc")]
use alloc::{borrow::ToOwned, string::String};
#[cfg(feature = "ecdsa")]
use crate::{public::ecdsa::EcdsaNistP256PublicKey, EcdsaCurve};
/// Default FIDO/U2F Security Key application string.
const DEFAULT_APPLICATION_STRING: &str = "ssh:";
/// Security Key (FIDO/U2F) ECDSA/NIST P-256 public key as specified in
/// [PROTOCOL.u2f](https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD).
#[cfg(feature = "ecdsa")]
#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
pub struct SkEcdsaSha2NistP256 {
/// Elliptic curve point representing a public key.
ec_point: EcdsaNistP256PublicKey,
/// FIDO/U2F application (typically `ssh:`)
#[cfg(feature = "alloc")]
application: String,
}
#[cfg(feature = "ecdsa")]
impl SkEcdsaSha2NistP256 {
/// Construct new instance of SkEcdsaSha2NistP256.
#[cfg(feature = "alloc")]
pub fn new(ec_point: EcdsaNistP256PublicKey, application: impl Into<String>) -> Self {
SkEcdsaSha2NistP256 {
ec_point,
application: application.into(),
}
}
/// Get the elliptic curve point for this Security Key.
pub fn ec_point(&self) -> &EcdsaNistP256PublicKey {
&self.ec_point
}
/// Get the FIDO/U2F application (typically `ssh:`).
#[cfg(not(feature = "alloc"))]
pub fn application(&self) -> &str {
DEFAULT_APPLICATION_STRING
}
/// Get the FIDO/U2F application (typically `ssh:`).
#[cfg(feature = "alloc")]
pub fn application(&self) -> &str {
&self.application
}
}
#[cfg(feature = "ecdsa")]
impl Decode for SkEcdsaSha2NistP256 {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
if EcdsaCurve::decode(reader)? != EcdsaCurve::NistP256 {
return Err(Error::Crypto);
}
let mut buf = [0u8; 65];
let ec_point = EcdsaNistP256PublicKey::from_bytes(reader.read_byten(&mut buf)?)?;
// application string (e.g. `ssh:`)
#[cfg(not(feature = "alloc"))]
reader.drain_prefixed()?;
Ok(Self {
ec_point,
#[cfg(feature = "alloc")]
application: String::decode(reader)?,
})
}
}
#[cfg(feature = "ecdsa")]
impl Encode for SkEcdsaSha2NistP256 {
fn encoded_len(&self) -> encoding::Result<usize> {
[
EcdsaCurve::NistP256.encoded_len()?,
self.ec_point.as_bytes().encoded_len()?,
self.application().encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
EcdsaCurve::NistP256.encode(writer)?;
self.ec_point.as_bytes().encode(writer)?;
self.application().encode(writer)?;
Ok(())
}
}
#[cfg(feature = "ecdsa")]
impl From<EcdsaNistP256PublicKey> for SkEcdsaSha2NistP256 {
fn from(ec_point: EcdsaNistP256PublicKey) -> SkEcdsaSha2NistP256 {
SkEcdsaSha2NistP256 {
ec_point,
#[cfg(feature = "alloc")]
application: DEFAULT_APPLICATION_STRING.to_owned(),
}
}
}
#[cfg(feature = "ecdsa")]
impl From<SkEcdsaSha2NistP256> for EcdsaNistP256PublicKey {
fn from(sk: SkEcdsaSha2NistP256) -> EcdsaNistP256PublicKey {
sk.ec_point
}
}
/// Security Key (FIDO/U2F) Ed25519 public key as specified in
/// [PROTOCOL.u2f](https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD).
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct SkEd25519 {
/// Ed25519 public key.
public_key: Ed25519PublicKey,
/// FIDO/U2F application (typically `ssh:`)
#[cfg(feature = "alloc")]
application: String,
}
impl SkEd25519 {
/// Construct new instance of SkEd25519.
#[cfg(feature = "alloc")]
pub fn new(public_key: Ed25519PublicKey, application: impl Into<String>) -> Self {
SkEd25519 {
public_key,
application: application.into(),
}
}
/// Get the Ed25519 private key for this security key.
pub fn public_key(&self) -> &Ed25519PublicKey {
&self.public_key
}
/// Get the FIDO/U2F application (typically `ssh:`).
#[cfg(not(feature = "alloc"))]
pub fn application(&self) -> &str {
DEFAULT_APPLICATION_STRING
}
/// Get the FIDO/U2F application (typically `ssh:`).
#[cfg(feature = "alloc")]
pub fn application(&self) -> &str {
&self.application
}
}
impl Decode for SkEd25519 {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let public_key = Ed25519PublicKey::decode(reader)?;
// application string (e.g. `ssh:`)
#[cfg(not(feature = "alloc"))]
reader.drain_prefixed()?;
Ok(Self {
public_key,
#[cfg(feature = "alloc")]
application: String::decode(reader)?,
})
}
}
impl Encode for SkEd25519 {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.public_key.encoded_len()?,
self.application().encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
self.public_key.encode(writer)?;
self.application().encode(writer)?;
Ok(())
}
}
impl From<Ed25519PublicKey> for SkEd25519 {
fn from(public_key: Ed25519PublicKey) -> SkEd25519 {
SkEd25519 {
public_key,
#[cfg(feature = "alloc")]
application: DEFAULT_APPLICATION_STRING.to_owned(),
}
}
}
impl From<SkEd25519> for Ed25519PublicKey {
fn from(sk: SkEd25519) -> Ed25519PublicKey {
sk.public_key
}
}

192
vendor/ssh-key/src/public/ssh_format.rs vendored Normal file
View File

@@ -0,0 +1,192 @@
//! Support for OpenSSH-formatted public keys, a.k.a. `SSH-format`.
//!
//! These keys have the form:
//!
//! ```text
//! <algorithm id> <base64 key data> <comment>
//! ```
//!
//! ## Example
//!
//! ```text
//! ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti user@example.com
//! ```
use crate::Result;
use core::str;
use encoding::{Base64Writer, Encode};
#[cfg(feature = "alloc")]
use {alloc::string::String, encoding::CheckedSum};
/// OpenSSH public key (a.k.a. `SSH-format`) decoder/encoder.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SshFormat<'a> {
/// Algorithm identifier
pub(crate) algorithm_id: &'a str,
/// Base64-encoded key data
pub(crate) base64_data: &'a [u8],
/// Comment
#[cfg_attr(not(feature = "alloc"), allow(dead_code))]
pub(crate) comment: &'a str,
}
impl<'a> SshFormat<'a> {
/// Parse the given binary data.
pub(crate) fn decode(mut bytes: &'a [u8]) -> Result<Self> {
let algorithm_id = decode_segment_str(&mut bytes)?;
let base64_data = decode_segment(&mut bytes)?;
let comment = str::from_utf8(bytes)?.trim_end();
if algorithm_id.is_empty() || base64_data.is_empty() {
// TODO(tarcieri): better errors for these cases?
return Err(encoding::Error::Length.into());
}
Ok(Self {
algorithm_id,
base64_data,
comment,
})
}
/// Encode data with OpenSSH public key encapsulation.
pub(crate) fn encode<'o, K>(
algorithm_id: &str,
key: &K,
comment: &str,
out: &'o mut [u8],
) -> Result<&'o str>
where
K: Encode,
{
let mut offset = 0;
encode_str(out, &mut offset, algorithm_id)?;
encode_str(out, &mut offset, " ")?;
let mut writer = Base64Writer::new(&mut out[offset..])?;
key.encode(&mut writer)?;
let base64_len = writer.finish()?.len();
offset = offset
.checked_add(base64_len)
.ok_or(encoding::Error::Length)?;
if !comment.is_empty() {
encode_str(out, &mut offset, " ")?;
encode_str(out, &mut offset, comment)?;
}
Ok(str::from_utf8(&out[..offset])?)
}
/// Encode string with OpenSSH public key encapsulation.
#[cfg(feature = "alloc")]
pub(crate) fn encode_string<K>(algorithm_id: &str, key: &K, comment: &str) -> Result<String>
where
K: Encode,
{
let encoded_len = [
2, // interstitial spaces
algorithm_id.len(),
base64_len_approx(key.encoded_len()?),
comment.len(),
]
.checked_sum()?;
let mut out = vec![0u8; encoded_len];
let actual_len = Self::encode(algorithm_id, key, comment, &mut out)?.len();
out.truncate(actual_len);
Ok(String::from_utf8(out)?)
}
}
/// Get the estimated length of data when encoded as Base64.
///
/// This is an upper bound where the actual length might be slightly shorter,
/// and can be used to estimate the capacity of an output buffer. However, the
/// final result may need to be sliced and should use the actual encoded length
/// rather than this estimate.
#[cfg(feature = "alloc")]
fn base64_len_approx(input_len: usize) -> usize {
(((input_len.saturating_mul(4)) / 3).saturating_add(3)) & !3
}
/// Parse a segment of the public key.
fn decode_segment<'a>(bytes: &mut &'a [u8]) -> Result<&'a [u8]> {
let start = *bytes;
let mut len = 0usize;
loop {
match *bytes {
[b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'+' | b'-' | b'/' | b'=' | b'@' | b'.', rest @ ..] =>
{
// Valid character; continue
*bytes = rest;
len = len.checked_add(1).ok_or(encoding::Error::Length)?;
}
[b' ', rest @ ..] => {
// Encountered space; we're done
*bytes = rest;
return start
.get(..len)
.ok_or_else(|| encoding::Error::Length.into());
}
[_, ..] => {
// Invalid character
return Err(encoding::Error::CharacterEncoding.into());
}
[] => {
// End of input, could be truncated or could be no comment
return start
.get(..len)
.ok_or_else(|| encoding::Error::Length.into());
}
}
}
}
/// Parse a segment of the public key as a `&str`.
fn decode_segment_str<'a>(bytes: &mut &'a [u8]) -> Result<&'a str> {
str::from_utf8(decode_segment(bytes)?).map_err(|_| encoding::Error::CharacterEncoding.into())
}
/// Encode a segment of the public key.
fn encode_str(out: &mut [u8], offset: &mut usize, s: &str) -> Result<()> {
let bytes = s.as_bytes();
if out.len()
< offset
.checked_add(bytes.len())
.ok_or(encoding::Error::Length)?
{
return Err(encoding::Error::Length.into());
}
out[*offset..][..bytes.len()].copy_from_slice(bytes);
*offset = offset
.checked_add(bytes.len())
.ok_or(encoding::Error::Length)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::SshFormat;
const EXAMPLE_KEY: &str = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti user@example.com";
#[test]
fn decode() {
let encapsulation = SshFormat::decode(EXAMPLE_KEY.as_bytes()).unwrap();
assert_eq!(encapsulation.algorithm_id, "ssh-ed25519");
assert_eq!(
encapsulation.base64_data,
b"AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti"
);
assert_eq!(encapsulation.comment, "user@example.com");
}
}

936
vendor/ssh-key/src/signature.rs vendored Normal file
View File

@@ -0,0 +1,936 @@
//! Signatures (e.g. CA signatures over SSH certificates)
use crate::{private, public, Algorithm, EcdsaCurve, Error, Mpint, PrivateKey, PublicKey, Result};
use alloc::vec::Vec;
use core::fmt;
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use signature::{SignatureEncoding, Signer, Verifier};
#[cfg(feature = "ed25519")]
use crate::{private::Ed25519Keypair, public::Ed25519PublicKey};
#[cfg(feature = "dsa")]
use {
crate::{private::DsaKeypair, public::DsaPublicKey},
bigint::BigUint,
sha1::Sha1,
signature::{DigestSigner, DigestVerifier},
};
#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
use crate::{
private::{EcdsaKeypair, EcdsaPrivateKey},
public::EcdsaPublicKey,
};
#[cfg(any(feature = "dsa", feature = "p256", feature = "p384", feature = "p521"))]
use core::iter;
#[cfg(feature = "rsa")]
use {
crate::{private::RsaKeypair, public::RsaPublicKey, HashAlg},
sha2::Sha512,
};
#[cfg(any(feature = "ed25519", feature = "rsa", feature = "p256"))]
use sha2::Sha256;
#[cfg(any(feature = "dsa", feature = "ed25519", feature = "p256"))]
use sha2::Digest;
const DSA_SIGNATURE_SIZE: usize = 40;
const ED25519_SIGNATURE_SIZE: usize = 64;
const SK_SIGNATURE_TRAILER_SIZE: usize = 5; // flags(u8), counter(u32)
const SK_ED25519_SIGNATURE_SIZE: usize = ED25519_SIGNATURE_SIZE + SK_SIGNATURE_TRAILER_SIZE;
/// Trait for signing keys which produce a [`Signature`].
///
/// This trait is automatically impl'd for any types which impl the
/// [`Signer`] trait for the SSH [`Signature`] type and also support a [`From`]
/// conversion for [`public::KeyData`].
pub trait SigningKey: Signer<Signature> {
/// Get the [`public::KeyData`] for this signing key.
fn public_key(&self) -> public::KeyData;
}
impl<T> SigningKey for T
where
T: Signer<Signature>,
public::KeyData: for<'a> From<&'a T>,
{
fn public_key(&self) -> public::KeyData {
self.into()
}
}
/// Low-level digital signature (e.g. DSA, ECDSA, Ed25519).
///
/// These are low-level signatures used as part of the OpenSSH certificate
/// format to represent signatures by certificate authorities (CAs), as well
/// as the higher-level [`SshSig`][`crate::SshSig`] format, which provides
/// general-purpose signing functionality using SSH keys.
///
/// From OpenSSH's [PROTOCOL.certkeys] specification:
///
/// > Signatures are computed and encoded according to the rules defined for
/// > the CA's public key algorithm ([RFC4253 section 6.6] for ssh-rsa and
/// > ssh-dss, [RFC5656] for the ECDSA types, and [RFC8032] for Ed25519).
///
/// RSA signature support is implemented using the SHA2 family extensions as
/// described in [RFC8332].
///
/// [PROTOCOL.certkeys]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
/// [RFC4253 section 6.6]: https://datatracker.ietf.org/doc/html/rfc4253#section-6.6
/// [RFC5656]: https://datatracker.ietf.org/doc/html/rfc5656
/// [RFC8032]: https://datatracker.ietf.org/doc/html/rfc8032
/// [RFC8332]: https://datatracker.ietf.org/doc/html/rfc8332
#[derive(Clone, Eq, PartialEq, PartialOrd, Ord)]
pub struct Signature {
/// Signature algorithm.
algorithm: Algorithm,
/// Raw signature serialized as algorithm-specific byte encoding.
data: Vec<u8>,
}
impl Signature {
/// Create a new signature with the given algorithm and raw signature data.
///
/// See specifications in toplevel [`Signature`] documentation for how to
/// format the raw signature data for a given algorithm.
///
/// # Returns
/// - [`Error::Encoding`] if the signature is not the correct length.
pub fn new(algorithm: Algorithm, data: impl Into<Vec<u8>>) -> Result<Self> {
let data = data.into();
// Validate signature is well-formed per OpensSH encoding
match algorithm {
Algorithm::Dsa if data.len() == DSA_SIGNATURE_SIZE => (),
Algorithm::Ecdsa { curve } => ecdsa_sig_size(&data, curve, false)?,
Algorithm::Ed25519 if data.len() == ED25519_SIGNATURE_SIZE => (),
Algorithm::SkEd25519 if data.len() == SK_ED25519_SIGNATURE_SIZE => (),
Algorithm::SkEcdsaSha2NistP256 => ecdsa_sig_size(&data, EcdsaCurve::NistP256, true)?,
Algorithm::Rsa { hash: Some(_) } => (),
Algorithm::Other(_) if !data.is_empty() => (),
_ => return Err(encoding::Error::Length.into()),
}
Ok(Self { algorithm, data })
}
/// Get the [`Algorithm`] associated with this signature.
pub fn algorithm(&self) -> Algorithm {
self.algorithm.clone()
}
/// Get the raw signature as bytes.
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
/// Placeholder signature used by the certificate builder.
///
/// This is guaranteed generate an error if anything attempts to encode it.
pub(crate) fn placeholder() -> Self {
Self {
algorithm: Algorithm::default(),
data: Vec::new(),
}
}
/// Check if this signature is the placeholder signature.
pub(crate) fn is_placeholder(&self) -> bool {
self.algorithm == Algorithm::default() && self.data.is_empty()
}
}
/// Returns Ok() if data holds an ecdsa signature with components of appropriate size
/// according to curve.
fn ecdsa_sig_size(data: &Vec<u8>, curve: EcdsaCurve, sk_trailer: bool) -> Result<()> {
let reader = &mut data.as_slice();
for _ in 0..2 {
let component = Mpint::decode(reader)?;
if component.as_positive_bytes().ok_or(Error::Crypto)?.len() > curve.field_size() {
return Err(encoding::Error::Length.into());
}
}
if sk_trailer {
reader.drain(SK_SIGNATURE_TRAILER_SIZE)?;
}
reader
.finish(())
.map_err(|_| encoding::Error::Length.into())
}
impl AsRef<[u8]> for Signature {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Decode for Signature {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let algorithm = Algorithm::decode(reader)?;
let mut data = Vec::decode(reader)?;
if algorithm == Algorithm::SkEd25519 || algorithm == Algorithm::SkEcdsaSha2NistP256 {
let flags = u8::decode(reader)?;
let counter = u32::decode(reader)?;
data.push(flags);
data.extend(counter.to_be_bytes());
}
Self::new(algorithm, data)
}
}
impl Encode for Signature {
fn encoded_len(&self) -> encoding::Result<usize> {
[
self.algorithm().encoded_len()?,
self.as_bytes().encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
if self.is_placeholder() {
return Err(encoding::Error::Length);
}
self.algorithm().encode(writer)?;
if self.algorithm == Algorithm::SkEd25519 {
let signature_length = self
.as_bytes()
.len()
.checked_sub(SK_SIGNATURE_TRAILER_SIZE)
.ok_or(encoding::Error::Length)?;
self.as_bytes()[..signature_length].encode(writer)?;
writer.write(&self.as_bytes()[signature_length..])?;
} else {
self.as_bytes().encode(writer)?;
}
Ok(())
}
}
impl SignatureEncoding for Signature {
type Repr = Vec<u8>;
}
/// Decode [`Signature`] from an [`Algorithm`]-prefixed OpenSSH-encoded bytestring.
impl TryFrom<&[u8]> for Signature {
type Error = Error;
fn try_from(mut bytes: &[u8]) -> Result<Self> {
Self::decode(&mut bytes)
}
}
impl TryFrom<Signature> for Vec<u8> {
type Error = Error;
fn try_from(signature: Signature) -> Result<Vec<u8>> {
let mut ret = Vec::<u8>::new();
signature.encode(&mut ret)?;
Ok(ret)
}
}
impl fmt::Debug for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Signature {{ algorithm: {:?}, data: {:X} }}",
self.algorithm, self
)
}
}
impl fmt::LowerHex for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_ref() {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
impl fmt::UpperHex for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_ref() {
write!(f, "{byte:02X}")?;
}
Ok(())
}
}
impl Signer<Signature> for PrivateKey {
fn try_sign(&self, message: &[u8]) -> signature::Result<Signature> {
self.key_data().try_sign(message)
}
}
impl Signer<Signature> for private::KeypairData {
#[allow(unused_variables)]
fn try_sign(&self, message: &[u8]) -> signature::Result<Signature> {
match self {
#[cfg(feature = "dsa")]
Self::Dsa(keypair) => keypair.try_sign(message),
#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
Self::Ecdsa(keypair) => keypair.try_sign(message),
#[cfg(feature = "ed25519")]
Self::Ed25519(keypair) => keypair.try_sign(message),
#[cfg(feature = "rsa")]
Self::Rsa(keypair) => keypair.try_sign(message),
_ => Err(self.algorithm()?.unsupported_error().into()),
}
}
}
impl Verifier<Signature> for PublicKey {
fn verify(&self, message: &[u8], signature: &Signature) -> signature::Result<()> {
self.key_data().verify(message, signature)
}
}
impl Verifier<Signature> for public::KeyData {
#[allow(unused_variables)]
fn verify(&self, message: &[u8], signature: &Signature) -> signature::Result<()> {
match self {
#[cfg(feature = "dsa")]
Self::Dsa(pk) => pk.verify(message, signature),
#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
Self::Ecdsa(pk) => pk.verify(message, signature),
#[cfg(feature = "ed25519")]
Self::Ed25519(pk) => pk.verify(message, signature),
#[cfg(feature = "ed25519")]
Self::SkEd25519(pk) => pk.verify(message, signature),
#[cfg(feature = "p256")]
Self::SkEcdsaSha2NistP256(pk) => pk.verify(message, signature),
#[cfg(feature = "rsa")]
Self::Rsa(pk) => pk.verify(message, signature),
#[allow(unreachable_patterns)]
_ => Err(self.algorithm().unsupported_error().into()),
}
}
}
#[cfg(feature = "dsa")]
impl Signer<Signature> for DsaKeypair {
fn try_sign(&self, message: &[u8]) -> signature::Result<Signature> {
let signature = dsa::SigningKey::try_from(self)?
.try_sign_digest(Sha1::new_with_prefix(message))
.map_err(|_| signature::Error::new())?;
// Encode the format specified in RFC4253 section 6.6: two raw 80-bit integers concatenated
let mut data = Vec::new();
for component in [signature.r(), signature.s()] {
let mut bytes = component.to_bytes_be();
let pad_len = (DSA_SIGNATURE_SIZE / 2).saturating_sub(bytes.len());
data.extend(iter::repeat(0).take(pad_len));
data.append(&mut bytes);
}
debug_assert_eq!(data.len(), DSA_SIGNATURE_SIZE);
Ok(Signature {
algorithm: Algorithm::Dsa,
data,
})
}
}
#[cfg(feature = "dsa")]
impl Verifier<Signature> for DsaPublicKey {
fn verify(&self, message: &[u8], signature: &Signature) -> signature::Result<()> {
match signature.algorithm {
Algorithm::Dsa => {
let data = signature.data.as_slice();
if data.len() != DSA_SIGNATURE_SIZE {
return Err(signature::Error::new());
}
let (r, s) = data.split_at(DSA_SIGNATURE_SIZE / 2);
let signature = dsa::Signature::from_components(
BigUint::from_bytes_be(r),
BigUint::from_bytes_be(s),
)?;
dsa::VerifyingKey::try_from(self)?
.verify_digest(Sha1::new_with_prefix(message), &signature)
.map_err(|_| signature::Error::new())
}
_ => Err(signature.algorithm().unsupported_error().into()),
}
}
}
#[cfg(feature = "ed25519")]
impl TryFrom<Signature> for ed25519_dalek::Signature {
type Error = Error;
fn try_from(signature: Signature) -> Result<ed25519_dalek::Signature> {
ed25519_dalek::Signature::try_from(&signature)
}
}
#[cfg(feature = "ed25519")]
impl TryFrom<&Signature> for ed25519_dalek::Signature {
type Error = Error;
fn try_from(signature: &Signature) -> Result<ed25519_dalek::Signature> {
match signature.algorithm {
Algorithm::Ed25519 | Algorithm::SkEd25519 => {
Ok(ed25519_dalek::Signature::try_from(signature.as_bytes())?)
}
_ => Err(Error::AlgorithmUnknown),
}
}
}
#[cfg(feature = "ed25519")]
impl Signer<Signature> for Ed25519Keypair {
fn try_sign(&self, message: &[u8]) -> signature::Result<Signature> {
let signature = ed25519_dalek::SigningKey::try_from(self)?.sign(message);
Ok(Signature {
algorithm: Algorithm::Ed25519,
data: signature.to_vec(),
})
}
}
#[cfg(feature = "ed25519")]
impl Verifier<Signature> for Ed25519PublicKey {
fn verify(&self, message: &[u8], signature: &Signature) -> signature::Result<()> {
let signature = ed25519_dalek::Signature::try_from(signature)?;
ed25519_dalek::VerifyingKey::try_from(self)?.verify(message, &signature)
}
}
#[cfg(feature = "ed25519")]
impl Verifier<Signature> for public::SkEd25519 {
fn verify(&self, message: &[u8], signature: &Signature) -> signature::Result<()> {
let (signature, flags_and_counter) = split_sk_signature(signature)?;
let signature = ed25519_dalek::Signature::try_from(signature)?;
ed25519_dalek::VerifyingKey::try_from(self.public_key())?.verify(
&make_sk_signed_data(self.application(), flags_and_counter, message),
&signature,
)
}
}
#[cfg(feature = "p256")]
impl Verifier<Signature> for public::SkEcdsaSha2NistP256 {
fn verify(&self, message: &[u8], signature: &Signature) -> signature::Result<()> {
let (signature_bytes, flags_and_counter) = split_sk_signature(signature)?;
let signature = p256_signature_from_openssh_bytes(signature_bytes)?;
p256::ecdsa::VerifyingKey::from_encoded_point(self.ec_point())?.verify(
&make_sk_signed_data(self.application(), flags_and_counter, message),
&signature,
)
}
}
#[cfg(any(feature = "p256", feature = "ed25519"))]
fn make_sk_signed_data(application: &str, flags_and_counter: &[u8], message: &[u8]) -> Vec<u8> {
const SHA256_OUTPUT_LENGTH: usize = 32;
const SIGNED_SK_DATA_LENGTH: usize = 2 * SHA256_OUTPUT_LENGTH + SK_SIGNATURE_TRAILER_SIZE;
let mut signed_data = Vec::with_capacity(SIGNED_SK_DATA_LENGTH);
signed_data.extend(Sha256::digest(application));
signed_data.extend(flags_and_counter);
signed_data.extend(Sha256::digest(message));
signed_data
}
#[cfg(any(feature = "p256", feature = "ed25519"))]
fn split_sk_signature(signature: &Signature) -> Result<(&[u8], &[u8])> {
let signature_bytes = signature.as_bytes();
let signature_len = signature_bytes
.len()
.checked_sub(SK_SIGNATURE_TRAILER_SIZE)
.ok_or(Error::Encoding(encoding::Error::Length))?;
Ok((
&signature_bytes[..signature_len],
&signature_bytes[signature_len..],
))
}
macro_rules! impl_signature_for_curve {
($krate:ident, $feature:expr, $curve:ident, $size:expr) => {
#[cfg(feature = $feature)]
impl TryFrom<$krate::ecdsa::Signature> for Signature {
type Error = Error;
fn try_from(signature: $krate::ecdsa::Signature) -> Result<Signature> {
Signature::try_from(&signature)
}
}
#[cfg(feature = $feature)]
impl TryFrom<&$krate::ecdsa::Signature> for Signature {
type Error = Error;
fn try_from(signature: &$krate::ecdsa::Signature) -> Result<Signature> {
let (r, s) = signature.split_bytes();
#[allow(clippy::arithmetic_side_effects)]
let mut data = Vec::with_capacity($size * 2 + 4 * 2 + 2);
Mpint::from_positive_bytes(&r)?.encode(&mut data)?;
Mpint::from_positive_bytes(&s)?.encode(&mut data)?;
Ok(Signature {
algorithm: Algorithm::Ecdsa {
curve: EcdsaCurve::$curve,
},
data,
})
}
}
#[cfg(feature = $feature)]
impl TryFrom<Signature> for $krate::ecdsa::Signature {
type Error = Error;
fn try_from(signature: Signature) -> Result<$krate::ecdsa::Signature> {
$krate::ecdsa::Signature::try_from(&signature)
}
}
#[cfg(feature = $feature)]
impl Signer<Signature> for EcdsaPrivateKey<$size> {
fn try_sign(&self, message: &[u8]) -> signature::Result<Signature> {
let signing_key = $krate::ecdsa::SigningKey::from_slice(self.as_ref())?;
let signature: $krate::ecdsa::Signature = signing_key.try_sign(message)?;
Ok(signature.try_into()?)
}
}
};
}
impl_signature_for_curve!(p256, "p256", NistP256, 32);
impl_signature_for_curve!(p384, "p384", NistP384, 48);
impl_signature_for_curve!(p521, "p521", NistP521, 66);
/// Build a generic sized object from a `u8` iterator, with leading zero padding
#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
fn zero_pad_field_bytes<B: FromIterator<u8> + Copy>(m: Mpint) -> Option<B> {
use core::mem::size_of;
let bytes = m.as_positive_bytes()?;
size_of::<B>()
.checked_sub(bytes.len())
.map(|i| B::from_iter(iter::repeat(0u8).take(i).chain(bytes.iter().cloned())))
}
#[cfg(feature = "p256")]
impl TryFrom<&Signature> for p256::ecdsa::Signature {
type Error = Error;
fn try_from(signature: &Signature) -> Result<p256::ecdsa::Signature> {
match signature.algorithm {
Algorithm::Ecdsa {
curve: EcdsaCurve::NistP256,
} => p256_signature_from_openssh_bytes(signature.as_bytes()),
_ => Err(signature.algorithm.clone().unsupported_error()),
}
}
}
#[cfg(feature = "p256")]
fn p256_signature_from_openssh_bytes(mut signature_bytes: &[u8]) -> Result<p256::ecdsa::Signature> {
let reader = &mut signature_bytes;
let r = Mpint::decode(reader)?;
let s = Mpint::decode(reader)?;
match (
zero_pad_field_bytes::<p256::FieldBytes>(r),
zero_pad_field_bytes::<p256::FieldBytes>(s),
) {
(Some(r), Some(s)) => Ok(p256::ecdsa::Signature::from_scalars(r, s)?),
_ => Err(Error::Crypto),
}
}
#[cfg(feature = "p384")]
impl TryFrom<&Signature> for p384::ecdsa::Signature {
type Error = Error;
fn try_from(signature: &Signature) -> Result<p384::ecdsa::Signature> {
match signature.algorithm {
Algorithm::Ecdsa {
curve: EcdsaCurve::NistP384,
} => {
let reader = &mut signature.as_bytes();
let r = Mpint::decode(reader)?;
let s = Mpint::decode(reader)?;
match (
zero_pad_field_bytes::<p384::FieldBytes>(r),
zero_pad_field_bytes::<p384::FieldBytes>(s),
) {
(Some(r), Some(s)) => Ok(p384::ecdsa::Signature::from_scalars(r, s)?),
_ => Err(Error::Crypto),
}
}
_ => Err(signature.algorithm.clone().unsupported_error()),
}
}
}
#[cfg(feature = "p521")]
impl TryFrom<&Signature> for p521::ecdsa::Signature {
type Error = Error;
fn try_from(signature: &Signature) -> Result<p521::ecdsa::Signature> {
match signature.algorithm {
Algorithm::Ecdsa {
curve: EcdsaCurve::NistP521,
} => {
let reader = &mut signature.as_bytes();
let r = Mpint::decode(reader)?;
let s = Mpint::decode(reader)?;
match (
zero_pad_field_bytes::<p521::FieldBytes>(r),
zero_pad_field_bytes::<p521::FieldBytes>(s),
) {
(Some(r), Some(s)) => Ok(p521::ecdsa::Signature::from_scalars(r, s)?),
_ => Err(Error::Crypto),
}
}
_ => Err(signature.algorithm.clone().unsupported_error()),
}
}
}
#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
impl Signer<Signature> for EcdsaKeypair {
fn try_sign(&self, message: &[u8]) -> signature::Result<Signature> {
match self {
#[cfg(feature = "p256")]
Self::NistP256 { private, .. } => private.try_sign(message),
#[cfg(feature = "p384")]
Self::NistP384 { private, .. } => private.try_sign(message),
#[cfg(feature = "p521")]
Self::NistP521 { private, .. } => private.try_sign(message),
#[cfg(not(all(feature = "p256", feature = "p384", feature = "p521")))]
_ => Err(self.algorithm().unsupported_error().into()),
}
}
}
#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
impl Verifier<Signature> for EcdsaPublicKey {
fn verify(&self, message: &[u8], signature: &Signature) -> signature::Result<()> {
match signature.algorithm {
Algorithm::Ecdsa { curve } => match curve {
#[cfg(feature = "p256")]
EcdsaCurve::NistP256 => {
let verifying_key = p256::ecdsa::VerifyingKey::try_from(self)?;
let signature = p256::ecdsa::Signature::try_from(signature)?;
verifying_key.verify(message, &signature)
}
#[cfg(feature = "p384")]
EcdsaCurve::NistP384 => {
let verifying_key = p384::ecdsa::VerifyingKey::try_from(self)?;
let signature = p384::ecdsa::Signature::try_from(signature)?;
verifying_key.verify(message, &signature)
}
#[cfg(feature = "p521")]
EcdsaCurve::NistP521 => {
let verifying_key = p521::ecdsa::VerifyingKey::try_from(self)?;
let signature = p521::ecdsa::Signature::try_from(signature)?;
verifying_key.verify(message, &signature)
}
#[cfg(not(all(feature = "p256", feature = "p384", feature = "p521")))]
_ => Err(signature.algorithm().unsupported_error().into()),
},
_ => Err(signature.algorithm().unsupported_error().into()),
}
}
}
#[cfg(feature = "rsa")]
impl Signer<Signature> for RsaKeypair {
fn try_sign(&self, message: &[u8]) -> signature::Result<Signature> {
let data = rsa::pkcs1v15::SigningKey::<Sha512>::try_from(self)?
.try_sign(message)
.map_err(|_| signature::Error::new())?;
Ok(Signature {
algorithm: Algorithm::Rsa {
hash: Some(HashAlg::Sha512),
},
data: data.to_vec(),
})
}
}
#[cfg(feature = "rsa")]
impl Verifier<Signature> for RsaPublicKey {
fn verify(&self, message: &[u8], signature: &Signature) -> signature::Result<()> {
match signature.algorithm {
Algorithm::Rsa { hash: Some(hash) } => {
let signature = rsa::pkcs1v15::Signature::try_from(signature.data.as_ref())?;
match hash {
HashAlg::Sha256 => rsa::pkcs1v15::VerifyingKey::<Sha256>::try_from(self)?
.verify(message, &signature)
.map_err(|_| signature::Error::new()),
HashAlg::Sha512 => rsa::pkcs1v15::VerifyingKey::<Sha512>::try_from(self)?
.verify(message, &signature)
.map_err(|_| signature::Error::new()),
}
}
_ => Err(signature.algorithm().unsupported_error().into()),
}
}
}
#[cfg(test)]
mod tests {
use super::Signature;
use crate::{Algorithm, EcdsaCurve, HashAlg};
use alloc::vec::Vec;
use encoding::Encode;
use hex_literal::hex;
#[cfg(feature = "ed25519")]
use {
super::Ed25519Keypair,
signature::{Signer, Verifier},
};
#[cfg(feature = "p256")]
use super::{zero_pad_field_bytes, Mpint};
const DSA_SIGNATURE: &[u8] = &hex!("000000077373682d6473730000002866725bf3c56100e975e21fff28a60f73717534d285ea3e1beefc2891f7189d00bd4d94627e84c55c");
const ECDSA_SHA2_P256_SIGNATURE: &[u8] = &hex!("0000001365636473612d736861322d6e6973747032353600000048000000201298ab320720a32139cda8a40c97a13dc54ce032ea3c6f09ea9e87501e48fa1d0000002046e4ac697a6424a9870b9ef04ca1182cd741965f989bd1f1f4a26fd83cf70348");
const ED25519_SIGNATURE: &[u8] = &hex!("0000000b7373682d65643235353139000000403d6b9906b76875aef1e7b2f1e02078a94f439aebb9a4734da1a851a81e22ce0199bbf820387a8de9c834c9c3cc778d9972dcbe70f68d53cc6bc9e26b02b46d04");
const SK_ED25519_SIGNATURE: &[u8] = &hex!("0000001a736b2d7373682d65643235353139406f70656e7373682e636f6d000000402f5670b6f93465d17423878a74084bf331767031ed240c627c8eb79ab8fa1b935a1fd993f52f5a13fec1797f8a434f943a6096246aea8dd5c8aa922cba3d95060100000009");
const RSA_SHA512_SIGNATURE: &[u8] = &hex!("0000000c7273612d736861322d3531320000018085a4ad1a91a62c00c85de7bb511f38088ff2bce763d76f4786febbe55d47624f9e2cffce58a680183b9ad162c7f0191ea26cab001ac5f5055743eced58e9981789305c208fc98d2657954e38eb28c7e7f3fbe92393a14324ed77aebb772a41aa7a107b38cb9bd1d9ad79b275135d1d7e019bb1d56d74f2450be6db0771f48f6707d3fcf9789592ca2e55595acc16b6e8d0139b56c5d1360b3a1e060f4151a3d7841df2c2a8c94d6f8a1bf633165ee0bcadac5642763df0dd79d3235ae5506595145f199d8abe8f9980411bf70a16e30f273736324d047043317044c36374d6a5ed34cac251e01c6795e4578393f9090bf4ae3e74a0009275a197315fc9c62f1c9aec1ba3b2d37c3b207e5500df19e090e7097ebc038fb9c9e35aea9161479ba6b5190f48e89e1abe51e8ec0e120ef89776e129687ca52d1892c8e88e6ef062a7d96b8a87682ca6a42ff1df0cdf5815c3645aeed7267ca7093043db0565e0f109b796bf117b9d2bb6d6debc0c67a4c9fb3aae3e29b00c7bd70f6c11cf53c295ff");
/// Example test vector for signing.
#[cfg(feature = "ed25519")]
const EXAMPLE_MSG: &[u8] = b"Hello, world!";
#[cfg(feature = "p256")]
#[test]
fn convert_ecdsa_sha2_p256() {
let p256_signature = p256::ecdsa::Signature::try_from(hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001").as_ref()).unwrap();
let _ssh_signature = Signature::try_from(p256_signature).unwrap();
}
#[cfg(feature = "p256")]
#[test]
fn zero_pad_field_bytes_p256() {
let i = Mpint::from_bytes(&hex!(
"1122334455667788112233445566778811223344556677881122334455667788"
))
.unwrap();
let fb = zero_pad_field_bytes::<p256::FieldBytes>(i);
assert!(fb.is_some());
// too long
let i = Mpint::from_bytes(&hex!(
"991122334455667788112233445566778811223344556677881122334455667788"
))
.unwrap();
let fb = zero_pad_field_bytes::<p256::FieldBytes>(i);
assert!(fb.is_none());
// short is okay
let i = Mpint::from_bytes(&hex!(
"22334455667788112233445566778811223344556677881122334455667788"
))
.unwrap();
let fb = zero_pad_field_bytes::<p256::FieldBytes>(i)
.expect("failed to build FieldBytes from short hex string");
assert_eq!(fb[0], 0x00);
assert_eq!(fb[1], 0x22);
}
#[test]
fn decode_dsa() {
let signature = Signature::try_from(DSA_SIGNATURE).unwrap();
assert_eq!(Algorithm::Dsa, signature.algorithm());
}
#[test]
fn decode_ecdsa_sha2_p256() {
let signature = Signature::try_from(ECDSA_SHA2_P256_SIGNATURE).unwrap();
assert_eq!(
Algorithm::Ecdsa {
curve: EcdsaCurve::NistP256
},
signature.algorithm()
);
}
#[test]
fn decode_ed25519() {
let signature = Signature::try_from(ED25519_SIGNATURE).unwrap();
assert_eq!(Algorithm::Ed25519, signature.algorithm());
}
#[test]
fn decode_sk_ed25519() {
let signature = Signature::try_from(SK_ED25519_SIGNATURE).unwrap();
assert_eq!(Algorithm::SkEd25519, signature.algorithm());
}
#[test]
fn decode_rsa() {
let signature = Signature::try_from(RSA_SHA512_SIGNATURE).unwrap();
assert_eq!(
Algorithm::Rsa {
hash: Some(HashAlg::Sha512)
},
signature.algorithm()
);
}
#[test]
fn encode_dsa() {
let signature = Signature::try_from(DSA_SIGNATURE).unwrap();
let mut result = Vec::new();
signature.encode(&mut result).unwrap();
assert_eq!(DSA_SIGNATURE, &result);
}
#[test]
fn encode_ecdsa_sha2_p256() {
let signature = Signature::try_from(ECDSA_SHA2_P256_SIGNATURE).unwrap();
let mut result = Vec::new();
signature.encode(&mut result).unwrap();
assert_eq!(ECDSA_SHA2_P256_SIGNATURE, &result);
}
#[test]
fn encode_ed25519() {
let signature = Signature::try_from(ED25519_SIGNATURE).unwrap();
let mut result = Vec::new();
signature.encode(&mut result).unwrap();
assert_eq!(ED25519_SIGNATURE, &result);
}
#[test]
fn encode_sk_ed25519() {
let signature = Signature::try_from(SK_ED25519_SIGNATURE).unwrap();
let mut result = Vec::new();
signature.encode(&mut result).unwrap();
assert_eq!(SK_ED25519_SIGNATURE, &result);
}
#[cfg(feature = "dsa")]
#[test]
fn try_sign_and_verify_dsa() {
use super::{DsaKeypair, DSA_SIGNATURE_SIZE};
use encoding::Decode as _;
use signature::{Signer as _, Verifier as _};
fn check_signature_component_lens(
keypair: &DsaKeypair,
data: &[u8],
r_len: usize,
s_len: usize,
) {
use sha1::{Digest as _, Sha1};
use signature::DigestSigner as _;
let signature = dsa::SigningKey::try_from(keypair)
.expect("valid DSA signing key")
.try_sign_digest(Sha1::new_with_prefix(data))
.expect("valid DSA signature");
let r = signature.r().to_bytes_be();
assert_eq!(
r.len(),
r_len,
"dsa signature component `r` has len {} != {}",
r.len(),
r_len
);
let s = signature.s().to_bytes_be();
assert_eq!(
s.len(),
s_len,
"dsa signature component `s` has len {} != {}",
s.len(),
s_len
);
}
let keypair = hex!("0000008100c161fb30c9e4e3602c8510f93bbd48d813da845dfcc75f3696e440cd019d609809608cd592b8430db901d7b43740740045b547c60fb035d69f9c64d3dfbfb13bb3edd8ccfdd44705739a639eb70f4aed16b0b8355de1b21cd9d442eff250895573a8af7ce2fb71fb062e887482dab5c68139845fb8afafc5f3819dc782920d510000001500f3fb6762430332bd5950edc5cd1ae6f17b88514f0000008061ef1394d864905e8efec3b610b7288a6522893af2a475f910796e0de47c8b065d365e942e80e471d1e6d4abdee1d3d3ede7103c6996432f1a9f9a671a31388672d63555077911fc69e641a997087260d22cdbf4965aa64bb382204f88987890ec225a5a7723a977dc1ecc5e04cf678f994692b20470adbf697489f800817b920000008100a9a6f1b65fc724d65df7441908b34af66489a4a3872cbbba25ea1bcfc83f25c4af1a62e339eefc814907cfaf0cb6d2d16996212a32a27a63013f01c57d0630f0be16c8c69d16fc25438e613b904b98aeb3e7c356fa8e75ee1d474c9f82f1280c5a6c18e9e607fcf7586eefb75ea9399da893b807375ac1396fd586bf277161980000001500ced95f1c7bbb39be4987837ad1f71be31bb7b0d9");
let keypair = DsaKeypair::decode(&mut &keypair[..]).expect("properly encoded DSA keypair");
let data = hex!("F0000040713d5f6fffe0000e6421ab0b3a69774d3da02fd72b107d6b32b6dad7c1660bbf507bf3eac3304cc5058f7e6f81b04239b8471459b1f3b387e2626f7eb8f6bcdd3200000006626c616465320000000e7373682d636f6e6e656374696f6e00000009686f73746261736564000000077373682d647373000001b2000000077373682d6473730000008100c161fb30c9e4e3602c8510f93bbd48d813da845dfcc75f3696e440cd019d609809608cd592b8430db901d7b43740740045b547c60fb035d69f9c64d3dfbfb13bb3edd8ccfdd44705739a639eb70f4aed16b0b8355de1b21cd9d442eff250895573a8af7ce2fb71fb062e887482dab5c68139845fb8afafc5f3819dc782920d510000001500f3fb6762430332bd5950edc5cd1ae6f17b88514f0000008061ef1394d864905e8efec3b610b7288a6522893af2a475f910796e0de47c8b065d365e942e80e471d1e6d4abdee1d3d3ede7103c6996432f1a9f9a671a31388672d63555077911fc69e641a997087260d22cdbf4965aa64bb382204f88987890ec225a5a7723a977dc1ecc5e04cf678f994692b20470adbf697489f800817b920000008100a9a6f1b65fc724d65df7441908b34af66489a4a3872cbbba25ea1bcfc83f25c4af1a62e339eefc814907cfaf0cb6d2d16996212a32a27a63013f01c57d0630f0be16c8c69d16fc25438e613b904b98aeb3e7c356fa8e75ee1d474c9f82f1280c5a6c18e9e607fcf7586eefb75ea9399da893b807375ac1396fd586bf2771619800000015746f6d61746f7373682e6c6f63616c646f6d61696e00000009746f6d61746f737368");
check_signature_component_lens(
&keypair,
&data,
DSA_SIGNATURE_SIZE / 2,
DSA_SIGNATURE_SIZE / 2,
);
let signature = keypair.try_sign(&data[..]).expect("dsa try_sign is ok");
keypair
.public
.verify(&data[..], &signature)
.expect("dsa verify is ok");
let data = hex!("00000040713d5f6fffe0000e6421ab0b3a69774d3da02fd72b107d6b32b6dad7c1660bbf507bf3eac3304cc5058f7e6f81b04239b8471459b1f3b387e2626f7eb8f6bcdd3200000006626c616465320000000e7373682d636f6e6e656374696f6e00000009686f73746261736564000000077373682d647373000001b2000000077373682d6473730000008100c161fb30c9e4e3602c8510f93bbd48d813da845dfcc75f3696e440cd019d609809608cd592b8430db901d7b43740740045b547c60fb035d69f9c64d3dfbfb13bb3edd8ccfdd44705739a639eb70f4aed16b0b8355de1b21cd9d442eff250895573a8af7ce2fb71fb062e887482dab5c68139845fb8afafc5f3819dc782920d510000001500f3fb6762430332bd5950edc5cd1ae6f17b88514f0000008061ef1394d864905e8efec3b610b7288a6522893af2a475f910796e0de47c8b065d365e942e80e471d1e6d4abdee1d3d3ede7103c6996432f1a9f9a671a31388672d63555077911fc69e641a997087260d22cdbf4965aa64bb382204f88987890ec225a5a7723a977dc1ecc5e04cf678f994692b20470adbf697489f800817b920000008100a9a6f1b65fc724d65df7441908b34af66489a4a3872cbbba25ea1bcfc83f25c4af1a62e339eefc814907cfaf0cb6d2d16996212a32a27a63013f01c57d0630f0be16c8c69d16fc25438e613b904b98aeb3e7c356fa8e75ee1d474c9f82f1280c5a6c18e9e607fcf7586eefb75ea9399da893b807375ac1396fd586bf2771619800000015746f6d61746f7373682e6c6f63616c646f6d61696e00000009746f6d61746f737368");
// verify that this data produces signature with `r` integer component that is less than 160 bits/20 bytes.
check_signature_component_lens(
&keypair,
&data,
DSA_SIGNATURE_SIZE / 2 - 1,
DSA_SIGNATURE_SIZE / 2,
);
let signature = keypair
.try_sign(&data[..])
.expect("dsa try_sign for r.len() == 19 is ok");
keypair
.public
.verify(&data[..], &signature)
.expect("dsa verify is ok");
}
#[cfg(feature = "ed25519")]
#[test]
fn sign_and_verify_ed25519() {
let keypair = Ed25519Keypair::from_seed(&[42; 32]);
let signature = keypair.sign(EXAMPLE_MSG);
assert!(keypair.public.verify(EXAMPLE_MSG, &signature).is_ok());
}
#[test]
fn placeholder() {
assert!(!Signature::try_from(ED25519_SIGNATURE)
.unwrap()
.is_placeholder());
let placeholder = Signature::placeholder();
assert!(placeholder.is_placeholder());
let mut writer = Vec::new();
assert_eq!(
placeholder.encode(&mut writer),
Err(encoding::Error::Length)
);
}
}

347
vendor/ssh-key/src/sshsig.rs vendored Normal file
View File

@@ -0,0 +1,347 @@
//! `sshsig` implementation.
use crate::{public, Algorithm, Error, HashAlg, Result, Signature, SigningKey};
use alloc::{string::String, string::ToString, vec::Vec};
use core::str::FromStr;
use encoding::{
pem::{LineEnding, PemLabel},
CheckedSum, Decode, DecodePem, Encode, EncodePem, Reader, Writer,
};
use signature::Verifier;
#[cfg(doc)]
use crate::{PrivateKey, PublicKey};
type Version = u32;
/// `sshsig` provides a general-purpose signature format based on SSH keys and
/// wire formats.
///
/// These signatures can be produced using `ssh-keygen -Y sign`. They're
/// encoded as PEM and begin with the following:
///
/// ```text
/// -----BEGIN SSH SIGNATURE-----
/// ```
///
/// See [PROTOCOL.sshsig] for more information.
///
/// # Usage
///
/// See [`PrivateKey::sign`] and [`PublicKey::verify`] for usage information.
///
/// [PROTOCOL.sshsig]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.sshsig?annotate=HEAD
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SshSig {
version: Version,
public_key: public::KeyData,
namespace: String,
reserved: Vec<u8>,
hash_alg: HashAlg,
signature: Signature,
}
impl SshSig {
/// Supported version.
pub const VERSION: Version = 1;
/// The preamble is the six-byte sequence "SSHSIG".
///
/// It is included to ensure that manual signatures can never be confused
/// with any message signed during SSH user or host authentication.
const MAGIC_PREAMBLE: &'static [u8] = b"SSHSIG";
/// Create a new signature with the given public key, namespace, hash
/// algorithm, and signature.
pub fn new(
public_key: public::KeyData,
namespace: impl Into<String>,
hash_alg: HashAlg,
signature: Signature,
) -> Result<Self> {
let version = Self::VERSION;
let namespace = namespace.into();
let reserved = Vec::new();
if namespace.is_empty() {
return Err(Error::Namespace);
}
Ok(Self {
version,
public_key,
namespace,
reserved,
hash_alg,
signature,
})
}
/// Decode signature from PEM which begins with the following:
///
/// ```text
/// -----BEGIN SSH SIGNATURE-----
/// ```
pub fn from_pem(pem: impl AsRef<[u8]>) -> Result<Self> {
Self::decode_pem(pem)
}
/// Encode signature as PEM which begins with the following:
///
/// ```text
/// -----BEGIN SSH SIGNATURE-----
/// ```
pub fn to_pem(&self, line_ending: LineEnding) -> Result<String> {
Ok(self.encode_pem_string(line_ending)?)
}
/// Sign the given message with the provided signing key.
///
/// See also: [`PrivateKey::sign`].
pub fn sign<S: SigningKey>(
signing_key: &S,
namespace: &str,
hash_alg: HashAlg,
msg: &[u8],
) -> Result<Self> {
if namespace.is_empty() {
return Err(Error::Namespace);
}
if signing_key.public_key().is_sk_ed25519() {
return Err(Algorithm::SkEd25519.unsupported_error());
}
#[cfg(feature = "ecdsa")]
if signing_key.public_key().is_sk_ecdsa_p256() {
return Err(Algorithm::SkEcdsaSha2NistP256.unsupported_error());
}
let signed_data = Self::signed_data(namespace, hash_alg, msg)?;
let signature = signing_key.try_sign(&signed_data)?;
Self::new(signing_key.public_key(), namespace, hash_alg, signature)
}
/// Get the raw message over which the signature for a given message
/// needs to be computed.
///
/// This is a low-level function intended for uses cases which can't be
/// expressed using [`SshSig::sign`], such as if the [`SigningKey`] trait
/// can't be used for some reason.
///
/// Once a [`Signature`] has been computed over the returned byte vector,
/// [`SshSig::new`] can be used to construct the final signature.
pub fn signed_data(namespace: &str, hash_alg: HashAlg, msg: &[u8]) -> Result<Vec<u8>> {
if namespace.is_empty() {
return Err(Error::Namespace);
}
SignedData {
namespace,
reserved: &[],
hash_alg,
hash: hash_alg.digest(msg).as_slice(),
}
.to_bytes()
}
/// Verify the given message against this signature.
///
/// Note that this method does not verify the public key or namespace
/// are correct and thus is crate-private so as to ensure these parameters
/// are always authenticated by users of the public API.
pub(crate) fn verify(&self, msg: &[u8]) -> Result<()> {
let signed_data = SignedData {
namespace: self.namespace.as_str(),
reserved: self.reserved.as_slice(),
hash_alg: self.hash_alg,
hash: self.hash_alg.digest(msg).as_slice(),
}
.to_bytes()?;
Ok(self.public_key.verify(&signed_data, &self.signature)?)
}
/// Get the signature algorithm.
pub fn algorithm(&self) -> Algorithm {
self.signature.algorithm()
}
/// Get version number for this signature.
///
/// Verifiers MUST reject signatures with versions greater than those
/// they support.
pub fn version(&self) -> Version {
self.version
}
/// Get public key which corresponds to the signing key that produced
/// this signature.
pub fn public_key(&self) -> &public::KeyData {
&self.public_key
}
/// Get the namespace (i.e. domain identifier) for this signature.
///
/// The purpose of the namespace value is to specify a unambiguous
/// interpretation domain for the signature, e.g. file signing.
/// This prevents cross-protocol attacks caused by signatures
/// intended for one intended domain being accepted in another.
/// The namespace value MUST NOT be the empty string.
pub fn namespace(&self) -> &str {
&self.namespace
}
/// Get reserved data associated with this signature. Typically empty.
///
/// The reserved value is present to encode future information
/// (e.g. tags) into the signature. Implementations should ignore
/// the reserved field if it is not empty.
pub fn reserved(&self) -> &[u8] {
&self.reserved
}
/// Get the hash algorithm used to produce this signature.
///
/// Data to be signed is first hashed with the specified `hash_alg`.
/// This is done to limit the amount of data presented to the signature
/// operation, which may be of concern if the signing key is held in limited
/// or slow hardware or on a remote ssh-agent. The supported hash algorithms
/// are "sha256" and "sha512".
pub fn hash_alg(&self) -> HashAlg {
self.hash_alg
}
/// Get the structured signature over the given message.
pub fn signature(&self) -> &Signature {
&self.signature
}
/// Get the bytes which comprise the serialized signature.
pub fn signature_bytes(&self) -> &[u8] {
self.signature.as_bytes()
}
}
impl Decode for SshSig {
type Error = Error;
fn decode(reader: &mut impl Reader) -> Result<Self> {
let mut magic_preamble = [0u8; Self::MAGIC_PREAMBLE.len()];
reader.read(&mut magic_preamble)?;
if magic_preamble != Self::MAGIC_PREAMBLE {
return Err(Error::FormatEncoding);
}
let version = Version::decode(reader)?;
if version > Self::VERSION {
return Err(Error::Version { number: version });
}
let public_key = reader.read_prefixed(public::KeyData::decode)?;
let namespace = String::decode(reader)?;
if namespace.is_empty() {
return Err(Error::Namespace);
}
let reserved = Vec::decode(reader)?;
let hash_alg = HashAlg::decode(reader)?;
let signature = reader.read_prefixed(Signature::decode)?;
Ok(Self {
version,
public_key,
namespace,
reserved,
hash_alg,
signature,
})
}
}
impl Encode for SshSig {
fn encoded_len(&self) -> encoding::Result<usize> {
[
Self::MAGIC_PREAMBLE.len(),
self.version.encoded_len()?,
self.public_key.encoded_len_prefixed()?,
self.namespace.encoded_len()?,
self.reserved.encoded_len()?,
self.hash_alg.encoded_len()?,
self.signature.encoded_len_prefixed()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
writer.write(Self::MAGIC_PREAMBLE)?;
self.version.encode(writer)?;
self.public_key.encode_prefixed(writer)?;
self.namespace.encode(writer)?;
self.reserved.encode(writer)?;
self.hash_alg.encode(writer)?;
self.signature.encode_prefixed(writer)?;
Ok(())
}
}
impl FromStr for SshSig {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::from_pem(s)
}
}
impl PemLabel for SshSig {
const PEM_LABEL: &'static str = "SSH SIGNATURE";
}
impl ToString for SshSig {
fn to_string(&self) -> String {
self.to_pem(LineEnding::default())
.expect("SSH signature encoding error")
}
}
/// Data to be signed.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct SignedData<'a> {
namespace: &'a str,
reserved: &'a [u8],
hash_alg: HashAlg,
hash: &'a [u8],
}
impl<'a> SignedData<'a> {
fn to_bytes(self) -> Result<Vec<u8>> {
let mut signed_bytes = Vec::with_capacity(self.encoded_len()?);
self.encode(&mut signed_bytes)?;
Ok(signed_bytes)
}
}
impl Encode for SignedData<'_> {
fn encoded_len(&self) -> encoding::Result<usize> {
[
SshSig::MAGIC_PREAMBLE.len(),
self.namespace.encoded_len()?,
self.reserved.encoded_len()?,
self.hash_alg.encoded_len()?,
self.hash.encoded_len()?,
]
.checked_sum()
}
fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
writer.write(SshSig::MAGIC_PREAMBLE)?;
self.namespace.encode(writer)?;
self.reserved.encode(writer)?;
self.hash_alg.encode(writer)?;
self.hash.encode(writer)?;
Ok(())
}
}