warg_crypto/signing/
private_key.rs1use super::{PublicKey, Signature, SignatureAlgorithm, SignatureAlgorithmParseError};
2use base64::{engine::general_purpose::STANDARD, Engine};
3use p256;
4use secrecy::{zeroize::Zeroizing, ExposeSecret, Secret, SecretString, SecretVec, Zeroize};
5use signature::Signer;
6use thiserror::Error;
7
8pub use signature::Error as SignatureError;
9
10pub struct PrivateKey(Secret<PrivateKeyInner>);
12
13pub enum PrivateKeyInner {
14 EcdsaP256(p256::ecdsa::SigningKey),
15}
16
17impl PrivateKey {
18 pub fn decode(s: impl Into<SecretString>) -> Result<Self, PrivateKeyParseError> {
20 let s = s.into();
21
22 let Some((algo, b64_data)) = s.expose_secret().split_once(':') else {
23 return Err(PrivateKeyParseError::MissingColon);
24 };
25
26 let algo = algo.parse::<SignatureAlgorithm>()?;
27 let bytes: SecretVec<u8> = STANDARD.decode(b64_data)?.into();
28
29 let key = match algo {
30 SignatureAlgorithm::EcdsaP256 => PrivateKeyInner::EcdsaP256(
31 p256::ecdsa::SigningKey::from_slice(bytes.expose_secret())?,
32 ),
33 };
34
35 Ok(PrivateKey(Secret::from(key)))
36 }
37
38 pub fn encode(&self) -> Zeroizing<String> {
40 Zeroizing::new(format!(
41 "{algo}:{b64}",
42 algo = self.signature_algorithm(),
43 b64 = STANDARD.encode(self.bytes())
44 ))
45 }
46
47 pub fn signature_algorithm(&self) -> SignatureAlgorithm {
49 match self.0.expose_secret() {
50 PrivateKeyInner::EcdsaP256(_) => SignatureAlgorithm::EcdsaP256,
51 }
52 }
53
54 pub fn bytes(&self) -> Vec<u8> {
56 match self.0.expose_secret() {
57 PrivateKeyInner::EcdsaP256(key) => key.to_bytes().to_vec(),
58 }
59 }
60
61 pub fn sign(&self, msg: &[u8]) -> Result<Signature, SignatureError> {
63 match self.0.expose_secret() {
64 PrivateKeyInner::EcdsaP256(key) => Ok(Signature::P256(key.try_sign(msg)?)),
65 }
66 }
67
68 pub fn public_key(&self) -> PublicKey {
69 match self.0.expose_secret() {
70 PrivateKeyInner::EcdsaP256(key) => {
71 PublicKey::EcdsaP256(p256::ecdsa::VerifyingKey::from(key))
72 }
73 }
74 }
75}
76
77impl TryFrom<String> for PrivateKey {
80 type Error = PrivateKeyParseError;
81
82 fn try_from(key: String) -> Result<Self, PrivateKeyParseError> {
83 let key = Zeroizing::new(key);
84
85 let Some((algo, b64_data)) = key.split_once(':') else {
86 return Err(PrivateKeyParseError::MissingColon);
87 };
88
89 let algo = algo.parse::<SignatureAlgorithm>()?;
90 let bytes = STANDARD.decode(b64_data)?;
91
92 let key = match algo {
93 SignatureAlgorithm::EcdsaP256 => PrivateKeyInner::EcdsaP256(
94 p256::ecdsa::SigningKey::from_bytes(bytes.as_slice().into())?,
95 ),
96 };
97
98 Ok(PrivateKey(Secret::from(key)))
99 }
100}
101
102#[derive(Error, Debug)]
103pub enum PrivateKeyParseError {
104 #[error("expected algorithm followed by colon")]
105 MissingColon,
106
107 #[error("unable to parse signature algorithm")]
108 SignatureAlgorithmParseError(#[from] SignatureAlgorithmParseError),
109
110 #[error("base64 decode failed")]
111 Base64DecodeError(#[from] base64::DecodeError),
112
113 #[error("private key could not be constructed from bytes")]
114 SignatureError(#[from] SignatureError),
115}
116
117impl Zeroize for PrivateKeyInner {
118 fn zeroize(&mut self) {
119 match self {
120 PrivateKeyInner::EcdsaP256(sk) => {
121 let mostly_zero = p256::ecdsa::SigningKey::from(
124 p256::NonZeroScalar::new(p256::Scalar::ONE).unwrap(),
125 );
126 drop(std::mem::replace(sk, mostly_zero));
127 }
128 }
129 }
130}
131
132impl From<p256::ecdsa::SigningKey> for PrivateKey {
133 fn from(key: p256::ecdsa::SigningKey) -> Self {
134 PrivateKey(Secret::from(PrivateKeyInner::EcdsaP256(key)))
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use pretty_assertions::assert_eq;
142
143 #[test]
144 fn test_roundtrip() {
145 let key_str = "ecdsa-p256:I+UlDo0HxyBBFeelhPPWmD+LnklOpqZDkrFP5VduASk=";
146 let key = PrivateKey::decode(key_str.to_string()).unwrap();
147 assert_eq!(key_str, &*key.encode());
148 }
149}