1use std::{fmt::Display, io::Read};
2
3use crate::{
4 errors::Result, prehash, signature::Signature, util::validate_comment, ErrorKind, SError,
5 SignatureBox, ALG_SIZE, COMPONENT_SIZE, KEY_SIG_ALG, KID_SIZE,
6};
7use base64::Engine;
8use ed25519_dalek::ed25519::{self, ComponentBytes};
9#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct PublicKeyBox<'s> {
14 pub(crate) untrusted_comment: Option<&'s str>,
15 pub(crate) public_key: PublicKey,
16}
17
18impl<'s> PublicKeyBox<'s> {
19 pub(crate) fn new(untrusted_comment: Option<&'s str>, public_key: PublicKey) -> Result<Self> {
20 validate_comment(untrusted_comment, ErrorKind::PublicKey)?;
21 Ok(Self {
22 untrusted_comment,
23 public_key,
24 })
25 }
26 pub fn from_verifying_key(
27 key: ed25519_dalek::VerifyingKey,
28 key_id: &[u8; 8],
29 untrusted_comment: Option<&'s str>,
30 ) -> Result<Self> {
31 let pk = RawPk::new(key.to_bytes());
32 let public_key = PublicKey::new(KEY_SIG_ALG, *key_id, pk);
33 Self::new(untrusted_comment, public_key)
34 }
35 #[allow(clippy::should_implement_trait)]
39 pub fn from_str(s: &'s str) -> Result<Self> {
40 parse_public_key(s)
41 }
42 pub fn from_raw_str(s: &'s str) -> Result<Self> {
45 let public_key = s.trim();
46 let decoder = base64::engine::general_purpose::STANDARD;
47 let pk_format = decoder
48 .decode(public_key.as_bytes())
49 .map_err(|e| SError::new(crate::ErrorKind::PublicKey, e))?;
50 if pk_format.len() != ALG_SIZE + KID_SIZE + COMPONENT_SIZE {
51 return Err(SError::new(
52 crate::ErrorKind::PublicKey,
53 "invalid public key length",
54 ));
55 }
56 let pk_sig_alg = &pk_format[..ALG_SIZE];
57 let pk_key_id = &pk_format[ALG_SIZE..ALG_SIZE + KID_SIZE];
58 let pk_key = &pk_format[ALG_SIZE + KID_SIZE..];
59 let pk = RawPk::new(pk_key.try_into().unwrap());
60 let public_key = PublicKey::new(
61 pk_sig_alg.try_into().unwrap(),
62 pk_key_id.try_into().unwrap(),
63 pk,
64 );
65 PublicKeyBox::new(None, public_key)
66 }
67 pub fn untrusted_comment(&self) -> Option<&'s str> {
69 self.untrusted_comment
70 }
71 pub(crate) fn verify_mini(
72 &self,
73 msg: &[u8],
74 sig: &Signature,
75 trusted_comment: Option<&str>,
76 ) -> Result<bool> {
77 if !(self.public_key.key.verify(msg, &sig.sig)?) {
78 return Err(SError::new(
79 crate::ErrorKind::PublicKey,
80 "verify sig failed",
81 ));
82 }
83 let mut global_data = vec![];
84 global_data.extend_from_slice(&sig.sig.to_bytes());
85 global_data.extend_from_slice(trusted_comment.unwrap_or("").as_bytes());
86 if !(self.public_key.key.verify(&global_data, &sig.global_sig)?) {
87 return Err(SError::new(
88 crate::ErrorKind::PublicKey,
89 "verify global sig failed",
90 ));
91 }
92 Ok(true)
93 }
94 pub(crate) fn self_verify(&self) -> Result<bool> {
95 if self.public_key.sig_alg != KEY_SIG_ALG {
96 return Err(SError::new(
97 crate::ErrorKind::PublicKey,
98 "invalid public key signature algorithm",
99 ));
100 }
101 Ok(true)
102 }
103 pub fn key_id(&self) -> &[u8; 8] {
105 &self.public_key.key_id
106 }
107 pub fn sig_alg(&self) -> &[u8; 2] {
109 &self.public_key.sig_alg
110 }
111 pub fn verify<R>(&self, signature_box: &SignatureBox, mut data_reader: R) -> Result<bool>
123 where
124 R: Read,
125 {
126 let prehashed = prehash(&mut data_reader)?;
127 verify_prehashed(self, signature_box, &prehashed)
128 }
129}
130pub(crate) fn verify_prehashed(
131 pk: &PublicKeyBox,
132 signature_box: &SignatureBox,
133 prehashed: &[u8],
134) -> Result<bool> {
135 if !signature_box.is_prehashed() {
136 return Err(SError::new(
137 ErrorKind::PrehashedMismatch,
138 "SignatureBox is not prehashed",
139 ));
140 }
141 if !pk.self_verify()? {
142 return Err(SError::new(
143 ErrorKind::PublicKey,
144 "public key self verification failed",
145 ));
146 }
147 if pk.public_key.key_id != *signature_box.key_id() {
148 return Err(SError::new(
149 ErrorKind::PublicKey,
150 "public key key_id mismatch",
151 ));
152 }
153 pk.verify_mini(
154 prehashed,
155 &signature_box.signature,
156 signature_box.trusted_comment(),
157 )
158}
159
160impl Display for PublicKeyBox<'_> {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 let mut s = String::new();
163 s.push_str("untrusted comment: ");
164 if let Some(c) = self.untrusted_comment {
165 s.push_str(c);
166 }
167 s.push('\n');
168 let encoder = base64::engine::general_purpose::STANDARD;
169 let mut pk_format = vec![];
170 pk_format.extend_from_slice(&self.public_key.sig_alg);
171 pk_format.extend_from_slice(&self.public_key.key_id);
172 pk_format.extend_from_slice(&self.public_key.key.0);
173 let pk = encoder.encode(&pk_format);
174 s.push_str(&pk);
175 s.push('\n');
176 write!(f, "{}", s)
177 }
178}
179fn parse_raw_public_key(public_key: &str) -> Result<PublicKey> {
180 let decoder = base64::engine::general_purpose::STANDARD;
181 let pk_format = decoder
182 .decode(public_key.as_bytes())
183 .map_err(|e| SError::new(crate::ErrorKind::PublicKey, e))?;
184 if pk_format.len() != ALG_SIZE + KID_SIZE + COMPONENT_SIZE {
185 return Err(SError::new(
186 crate::ErrorKind::PublicKey,
187 "invalid public key length",
188 ));
189 }
190 let pk_sig_alg = &pk_format[..ALG_SIZE];
191 let pk_key_id = &pk_format[ALG_SIZE..ALG_SIZE + KID_SIZE];
192 let pk_key = &pk_format[ALG_SIZE + KID_SIZE..];
193 let pk = RawPk::new(pk_key.try_into().unwrap());
194 let public_key = PublicKey::new(
195 pk_sig_alg.try_into().unwrap(),
196 pk_key_id.try_into().unwrap(),
197 pk,
198 );
199 Ok(public_key)
200}
201fn parse_public_key(s: &str) -> Result<PublicKeyBox<'_>> {
202 let mut lines = s.lines();
203 let untrusted_comment = lines
204 .next()
205 .ok_or_else(|| SError::new(crate::ErrorKind::PublicKey, "empty public key"))?
206 .strip_prefix("untrusted comment: ")
207 .ok_or_else(|| SError::new(crate::ErrorKind::PublicKey, "missing untrusted comment"))?;
208 validate_comment(Some(untrusted_comment), ErrorKind::PublicKey)?;
209 let public_key = lines
210 .next()
211 .ok_or_else(|| SError::new(crate::ErrorKind::PublicKey, "missing public key"))?;
212 if lines.next().is_some() {
213 return Err(SError::new(
214 crate::ErrorKind::PublicKey,
215 "unexpected extra data",
216 ));
217 }
218 PublicKeyBox::new(Some(untrusted_comment), parse_raw_public_key(public_key)?)
219}
220#[cfg(test)]
221#[test]
222fn test_parse_public_key() {
223 use crate::KeyPairBox;
224 let password = b"password";
225 let k = KeyPairBox::generate(Some(password), None, None).unwrap();
226 let file = k.public_key_box.to_string();
227 let pk = parse_public_key(&file).unwrap();
228 assert_eq!(file, pk.to_string());
229}
230#[cfg(test)]
231#[test]
232fn test_parse_public_key_rejects_injected_comment() {
233 use crate::KeyPairBox;
234
235 let victim = KeyPairBox::generate(Some(b"victim"), None, None).unwrap();
236 let attacker = KeyPairBox::generate(Some(b"attacker"), None, None).unwrap();
237 let victim_raw = victim
238 .public_key_box
239 .to_string()
240 .lines()
241 .nth(1)
242 .unwrap()
243 .to_owned();
244 let attacker_raw = attacker
245 .public_key_box
246 .to_string()
247 .lines()
248 .nth(1)
249 .unwrap()
250 .to_owned();
251 let injected = format!("untrusted comment: comment\n{attacker_raw}\n{victim_raw}\n");
252
253 assert!(parse_public_key(&injected).is_err());
254}
255#[cfg(test)]
256#[test]
257fn test_parse_public_key_requires_comment_prefix() {
258 use crate::KeyPairBox;
259
260 let keypair = KeyPairBox::generate(Some(b"password"), None, None).unwrap();
261 let public_key = keypair
262 .public_key_box
263 .to_string()
264 .lines()
265 .nth(1)
266 .unwrap()
267 .to_owned();
268 let malformed = format!("bad comment\n{public_key}\n");
269
270 assert!(parse_public_key(&malformed).is_err());
271}
272#[derive(Clone, Debug, PartialEq, Eq)]
274pub(crate) struct PublicKey {
275 pub sig_alg: [u8; 2],
276 pub key_id: [u8; 8],
277 pub key: RawPk,
278}
279impl PublicKey {
280 pub fn new(sig_alg: [u8; 2], key_id: [u8; 8], key: RawPk) -> Self {
281 Self {
282 sig_alg,
283 key_id,
284 key,
285 }
286 }
287}
288#[derive(Clone, Debug, PartialEq, Eq)]
289pub(crate) struct RawPk(pub ComponentBytes);
290impl RawPk {
291 pub fn new(key: ComponentBytes) -> Self {
292 Self(key)
293 }
294 pub fn verify(&self, msg: &[u8], sig: &ed25519::Signature) -> Result<bool> {
295 let pk = ed25519_dalek::VerifyingKey::from_bytes(&self.0)?;
296 Ok(pk.verify_strict(msg, sig).map(|_| true)?)
297 }
298}