ssh_agent_lib/proto/
privatekey.rs

1//! Types for handling SSH private key data.
2
3use core::fmt;
4
5use ssh_encoding::{Decode, Encode, Reader, Writer};
6use ssh_key::{
7    private::{self, DsaPrivateKey, Ed25519Keypair, RsaPrivateKey},
8    Algorithm, EcdsaCurve, Error, Result,
9};
10use subtle::{Choice, ConstantTimeEq};
11
12/// Elliptic Curve Digital Signature Algorithm (ECDSA) private/public key pair.
13#[derive(Clone, Debug)]
14pub enum EcdsaPrivateKey {
15    /// NIST P-256 ECDSA private key.
16    NistP256(private::EcdsaPrivateKey<32>),
17
18    /// NIST P-384 ECDSA private key.
19    NistP384(private::EcdsaPrivateKey<48>),
20
21    /// NIST P-521 ECDSA private key.
22    NistP521(private::EcdsaPrivateKey<66>),
23}
24
25impl ConstantTimeEq for EcdsaPrivateKey {
26    fn ct_eq(&self, other: &Self) -> Choice {
27        let private_key_a = match self {
28            Self::NistP256(private) => private.as_slice(),
29            Self::NistP384(private) => private.as_slice(),
30            Self::NistP521(private) => private.as_slice(),
31        };
32
33        let private_key_b = match other {
34            Self::NistP256(private) => private.as_slice(),
35            Self::NistP384(private) => private.as_slice(),
36            Self::NistP521(private) => private.as_slice(),
37        };
38
39        private_key_a.ct_eq(private_key_b)
40    }
41}
42
43impl Eq for EcdsaPrivateKey {}
44
45impl PartialEq for EcdsaPrivateKey {
46    fn eq(&self, other: &Self) -> bool {
47        self.ct_eq(other).into()
48    }
49}
50
51impl EcdsaPrivateKey {
52    fn decode_as(reader: &mut impl Reader, curve: EcdsaCurve) -> Result<Self> {
53        match curve {
54            EcdsaCurve::NistP256 => {
55                private::EcdsaPrivateKey::<32>::decode(reader).map(Self::NistP256)
56            }
57            EcdsaCurve::NistP384 => {
58                private::EcdsaPrivateKey::<48>::decode(reader).map(Self::NistP384)
59            }
60            EcdsaCurve::NistP521 => {
61                private::EcdsaPrivateKey::<66>::decode(reader).map(Self::NistP521)
62            }
63        }
64    }
65}
66
67impl Encode for EcdsaPrivateKey {
68    fn encoded_len(&self) -> ssh_encoding::Result<usize> {
69        match self {
70            Self::NistP256(private) => private.encoded_len(),
71            Self::NistP384(private) => private.encoded_len(),
72            Self::NistP521(private) => private.encoded_len(),
73        }
74    }
75
76    fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
77        match self {
78            Self::NistP256(private) => private.encode(writer),
79            Self::NistP384(private) => private.encode(writer),
80            Self::NistP521(private) => private.encode(writer),
81        }
82    }
83}
84
85/// Private key data stored within a `Credential` object
86#[derive(Clone)]
87#[non_exhaustive]
88pub enum PrivateKeyData {
89    /// Digital Signature Algorithm (DSA) private key.
90    Dsa(DsaPrivateKey),
91
92    /// ECDSA private key.
93    Ecdsa(EcdsaPrivateKey),
94
95    // Note: OpenSSH is a little inconsistent, Ed25519 is the only one
96    // algorithm that will always encode the full key pair.
97    /// Ed25519 key pair.
98    Ed25519(Ed25519Keypair),
99
100    /// RSA private key.
101    Rsa(RsaPrivateKey),
102}
103
104impl PrivateKeyData {
105    /// Decode [`PrivateKeyData`] for the specified algorithm.
106    pub fn decode_as(reader: &mut impl Reader, algorithm: Algorithm) -> Result<Self> {
107        match algorithm {
108            Algorithm::Dsa => DsaPrivateKey::decode(reader).map(Self::Dsa),
109            Algorithm::Ecdsa { curve } => {
110                EcdsaPrivateKey::decode_as(reader, curve).map(Self::Ecdsa)
111            }
112            Algorithm::Ed25519 => Ed25519Keypair::decode(reader).map(Self::Ed25519),
113            Algorithm::Rsa { .. } => RsaPrivateKey::decode(reader).map(Self::Rsa),
114            #[allow(unreachable_patterns)]
115            _ => Err(Error::AlgorithmUnknown),
116        }
117    }
118}
119
120impl fmt::Debug for PrivateKeyData {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        match self {
123            Self::Dsa(_) => write!(f, "PrivateKeyData::Dsa"),
124            Self::Ecdsa(_) => write!(f, "PrivateKeyData::Ecdsa"),
125            Self::Ed25519(_) => write!(f, "PrivateKeyData::Ed25519"),
126            Self::Rsa(_) => write!(f, "PrivateKeyData::Rsa"),
127        }
128    }
129}
130
131impl Encode for PrivateKeyData {
132    fn encoded_len(&self) -> ssh_encoding::Result<usize> {
133        match self {
134            Self::Dsa(key) => key.encoded_len(),
135            Self::Ecdsa(key) => key.encoded_len(),
136            Self::Ed25519(key) => key.encoded_len(),
137            Self::Rsa(key) => key.encoded_len(),
138        }
139    }
140
141    fn encode(&self, writer: &mut impl Writer) -> ssh_encoding::Result<()> {
142        match self {
143            Self::Dsa(key) => key.encode(writer)?,
144            Self::Ecdsa(key) => key.encode(writer)?,
145            Self::Ed25519(key) => key.encode(writer)?,
146            Self::Rsa(key) => key.encode(writer)?,
147        }
148
149        Ok(())
150    }
151}
152
153impl ConstantTimeEq for PrivateKeyData {
154    fn ct_eq(&self, other: &Self) -> Choice {
155        // Note: constant-time with respect to key *data* comparisons, not algorithms
156        match (self, other) {
157            (Self::Dsa(a), Self::Dsa(b)) => a.ct_eq(b),
158            (Self::Ecdsa(a), Self::Ecdsa(b)) => a.ct_eq(b),
159            (Self::Ed25519(a), Self::Ed25519(b)) => a.ct_eq(b),
160            (Self::Rsa(a), Self::Rsa(b)) => a.ct_eq(b),
161            #[allow(unreachable_patterns)]
162            _ => Choice::from(0),
163        }
164    }
165}
166
167impl Eq for PrivateKeyData {}
168
169impl PartialEq for PrivateKeyData {
170    fn eq(&self, other: &Self) -> bool {
171        self.ct_eq(other).into()
172    }
173}