Skip to main content

ssh_key/public/
ecdsa.rs

1//! Elliptic Curve Digital Signature Algorithm (ECDSA) public keys.
2
3use crate::{Algorithm, EcdsaCurve, Error, Result};
4use core::fmt;
5use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
6use sec1::consts::{U32, U48, U66};
7
8/// ECDSA/NIST P-256 public key.
9pub(super) type EcdsaNistP256PublicKey = sec1::EncodedPoint<U32>;
10
11/// ECDSA/NIST P-384 public key.
12pub(super) type EcdsaNistP384PublicKey = sec1::EncodedPoint<U48>;
13
14/// ECDSA/NIST P-521 public key.
15pub(super) type EcdsaNistP521PublicKey = sec1::EncodedPoint<U66>;
16
17/// Elliptic Curve Digital Signature Algorithm (ECDSA) public key.
18///
19/// Public keys are represented as [`sec1::EncodedPoint`] and require the
20/// `sec1` feature of this crate is enabled (which it is by default).
21///
22/// Described in [FIPS 186-4](https://csrc.nist.gov/publications/detail/fips/186/4/final).
23#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
24pub enum EcdsaPublicKey {
25    /// NIST P-256 ECDSA public key.
26    NistP256(EcdsaNistP256PublicKey),
27
28    /// NIST P-384 ECDSA public key.
29    NistP384(EcdsaNistP384PublicKey),
30
31    /// NIST P-521 ECDSA public key.
32    NistP521(EcdsaNistP521PublicKey),
33}
34
35impl EcdsaPublicKey {
36    /// Maximum size of a SEC1-encoded ECDSA public key (i.e. curve point).
37    ///
38    /// This is the size of 2 * P-521 field elements (2 * 66 = 132) which
39    /// represent the affine coordinates of a curve point plus one additional
40    /// byte for the SEC1 "tag" identifying the curve point encoding.
41    const MAX_SIZE: usize = 133;
42
43    /// Parse an ECDSA public key from a SEC1-encoded point.
44    ///
45    /// Determines the key type from the SEC1 tag byte and length.
46    ///
47    /// # Errors
48    /// Returns [`Error::Encoding`] in the event of an encoding error.
49    pub fn from_sec1_bytes(bytes: &[u8]) -> Result<Self> {
50        match bytes {
51            [tag, rest @ ..] => {
52                let point_size = match sec1::point::Tag::from_u8(*tag)? {
53                    sec1::point::Tag::CompressedEvenY | sec1::point::Tag::CompressedOddY => {
54                        rest.len()
55                    }
56                    #[allow(clippy::integer_division_remainder_used, reason = "public input")]
57                    sec1::point::Tag::Uncompressed => rest.len() / 2,
58                    _ => return Err(Error::FormatEncoding),
59                };
60
61                match point_size {
62                    32 => Ok(Self::NistP256(EcdsaNistP256PublicKey::from_bytes(bytes)?)),
63                    48 => Ok(Self::NistP384(EcdsaNistP384PublicKey::from_bytes(bytes)?)),
64                    66 => Ok(Self::NistP521(EcdsaNistP521PublicKey::from_bytes(bytes)?)),
65                    _ => Err(encoding::Error::Length.into()),
66                }
67            }
68            _ => Err(encoding::Error::Length.into()),
69        }
70    }
71
72    /// Borrow the SEC1-encoded key data as bytes.
73    #[must_use]
74    pub fn as_sec1_bytes(&self) -> &[u8] {
75        match self {
76            EcdsaPublicKey::NistP256(point) => point.as_bytes(),
77            EcdsaPublicKey::NistP384(point) => point.as_bytes(),
78            EcdsaPublicKey::NistP521(point) => point.as_bytes(),
79        }
80    }
81
82    /// Get the [`Algorithm`] for this public key type.
83    #[must_use]
84    pub fn algorithm(&self) -> Algorithm {
85        Algorithm::Ecdsa {
86            curve: self.curve(),
87        }
88    }
89
90    /// Get the [`EcdsaCurve`] for this key.
91    #[must_use]
92    pub fn curve(&self) -> EcdsaCurve {
93        match self {
94            EcdsaPublicKey::NistP256(_) => EcdsaCurve::NistP256,
95            EcdsaPublicKey::NistP384(_) => EcdsaCurve::NistP384,
96            EcdsaPublicKey::NistP521(_) => EcdsaCurve::NistP521,
97        }
98    }
99}
100
101impl AsRef<[u8]> for EcdsaPublicKey {
102    fn as_ref(&self) -> &[u8] {
103        self.as_sec1_bytes()
104    }
105}
106
107impl Decode for EcdsaPublicKey {
108    type Error = Error;
109
110    fn decode(reader: &mut impl Reader) -> Result<Self> {
111        let curve = EcdsaCurve::decode(reader)?;
112
113        let mut buf = [0u8; Self::MAX_SIZE];
114        let key = Self::from_sec1_bytes(reader.read_byten(&mut buf)?)?;
115
116        if key.curve() == curve {
117            Ok(key)
118        } else {
119            Err(Error::AlgorithmUnknown)
120        }
121    }
122}
123
124impl Encode for EcdsaPublicKey {
125    fn encoded_len(&self) -> encoding::Result<usize> {
126        [
127            self.curve().encoded_len()?,
128            4, // uint32 length prefix
129            self.as_ref().len(),
130        ]
131        .checked_sum()
132    }
133
134    fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
135        self.curve().encode(writer)?;
136        self.as_ref().encode(writer)?;
137        Ok(())
138    }
139}
140
141impl fmt::Display for EcdsaPublicKey {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        write!(f, "{self:X}")
144    }
145}
146
147impl fmt::LowerHex for EcdsaPublicKey {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        for byte in self.as_sec1_bytes() {
150            write!(f, "{byte:02x}")?;
151        }
152        Ok(())
153    }
154}
155
156impl fmt::UpperHex for EcdsaPublicKey {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        for byte in self.as_sec1_bytes() {
159            write!(f, "{byte:02X}")?;
160        }
161        Ok(())
162    }
163}
164
165macro_rules! impl_ecdsa_for_curve {
166    ($krate:ident, $feature:expr, $curve:ident) => {
167        #[cfg(feature = $feature)]
168        impl TryFrom<EcdsaPublicKey> for $krate::ecdsa::VerifyingKey {
169            type Error = Error;
170
171            fn try_from(key: EcdsaPublicKey) -> Result<$krate::ecdsa::VerifyingKey> {
172                $krate::ecdsa::VerifyingKey::try_from(&key)
173            }
174        }
175
176        #[cfg(feature = $feature)]
177        impl TryFrom<&EcdsaPublicKey> for $krate::ecdsa::VerifyingKey {
178            type Error = Error;
179
180            fn try_from(public_key: &EcdsaPublicKey) -> Result<$krate::ecdsa::VerifyingKey> {
181                match public_key {
182                    EcdsaPublicKey::$curve(key) => {
183                        $krate::ecdsa::VerifyingKey::from_sec1_point(key).map_err(|_| Error::Crypto)
184                    }
185                    _ => Err(Error::AlgorithmUnknown),
186                }
187            }
188        }
189
190        #[cfg(feature = $feature)]
191        impl From<$krate::ecdsa::VerifyingKey> for EcdsaPublicKey {
192            fn from(key: $krate::ecdsa::VerifyingKey) -> EcdsaPublicKey {
193                EcdsaPublicKey::from(&key)
194            }
195        }
196
197        #[cfg(feature = $feature)]
198        impl From<&$krate::ecdsa::VerifyingKey> for EcdsaPublicKey {
199            fn from(key: &$krate::ecdsa::VerifyingKey) -> EcdsaPublicKey {
200                EcdsaPublicKey::$curve(key.to_sec1_point(false))
201            }
202        }
203    };
204}
205
206impl_ecdsa_for_curve!(p256, "p256", NistP256);
207impl_ecdsa_for_curve!(p384, "p384", NistP384);
208impl_ecdsa_for_curve!(p521, "p521", NistP521);