1use std::fmt::Display;
2use std::vec;
3
4use base64::Engine;
5use ed25519_dalek::ed25519;
6
7use crate::errors::Result;
8use crate::util::validate_comment;
9use crate::{ErrorKind, SError, ALG_SIZE, KID_SIZE, SIGALG_PREHASHED, SIG_SIZE};
10#[derive(Debug, Clone)]
19pub struct SignatureBox<'s> {
20 pub(crate) untrusted_comment: Option<&'s str>,
21 pub(crate) trusted_comment: Option<&'s str>,
22 pub(crate) signature: Signature,
23}
24fn parse_signature(s: &'_ str) -> Result<SignatureBox<'_>> {
25 let mut lines = s.lines();
26 let untrusted_comment = lines
27 .next()
28 .ok_or_else(|| {
29 SError::new(
30 crate::ErrorKind::SignatureError,
31 "missing untrusted comment",
32 )
33 })?
34 .strip_prefix("untrusted comment: ")
35 .ok_or_else(|| {
36 SError::new(
37 crate::ErrorKind::SignatureError,
38 "missing untrusted comment",
39 )
40 })?;
41 validate_comment(Some(untrusted_comment), ErrorKind::SignatureError)?;
42 let sig = lines
43 .next()
44 .ok_or_else(|| SError::new(crate::ErrorKind::SignatureError, "missing signature"))?;
45 let decoder = base64::engine::general_purpose::STANDARD;
46 let sig_format = decoder
47 .decode(sig.as_bytes())
48 .map_err(|e| SError::new(crate::ErrorKind::SignatureError, e))?;
49 if sig_format.len() != ALG_SIZE + KID_SIZE + SIG_SIZE {
50 return Err(SError::new(
51 crate::ErrorKind::SignatureError,
52 "invalid signature length",
53 ));
54 }
55 let sig_alg = &sig_format[..ALG_SIZE];
56 let key_id = &sig_format[ALG_SIZE..ALG_SIZE + KID_SIZE];
57 let sig = &sig_format[ALG_SIZE + KID_SIZE..];
58 let trusted_comment = lines
59 .next()
60 .ok_or_else(|| SError::new(crate::ErrorKind::SignatureError, "missing trusted comment"))?
61 .strip_prefix("trusted comment: ")
62 .ok_or_else(|| SError::new(crate::ErrorKind::SignatureError, "missing trusted comment"))?;
63 validate_comment(Some(trusted_comment), ErrorKind::SignatureError)?;
64 let global_sig = lines
65 .next()
66 .ok_or_else(|| SError::new(crate::ErrorKind::SignatureError, "missing global signature"))?;
67 if lines.next().is_some() {
68 return Err(SError::new(
69 crate::ErrorKind::SignatureError,
70 "unexpected extra data",
71 ));
72 }
73 let global_sig_format = decoder
74 .decode(global_sig.as_bytes())
75 .map_err(|e| SError::new(crate::ErrorKind::SignatureError, e))?;
76 if global_sig_format.len() != 64 {
77 return Err(SError::new(
78 crate::ErrorKind::SignatureError,
79 "invalid global signature length",
80 ));
81 }
82 SignatureBox::new(
83 Some(untrusted_comment),
84 Some(trusted_comment),
85 Signature::new(
86 sig_alg.try_into().unwrap(),
87 key_id.try_into().unwrap(),
88 sig.try_into().unwrap(),
89 ed25519::Signature::from_bytes(&global_sig_format.try_into().unwrap()),
90 ),
91 )
92}
93
94#[cfg(test)]
95#[test]
96fn test_parse_signature() {
97 use crate::{sign, KeyPairBox};
98
99 let password = b"password";
100 let k = KeyPairBox::generate(Some(password), None, None).unwrap();
101 let file = sign(
102 Some(&k.public_key_box),
103 &k.secret_key_box,
104 Some(password),
105 "test".as_bytes(),
106 Some("trusted comment"),
107 Some("untrusted comment"),
108 )
109 .unwrap()
110 .to_string();
111 let sig = parse_signature(&file).unwrap();
112 assert_eq!(file, sig.to_string());
113}
114#[cfg(test)]
115#[test]
116fn test_parse_signature_rejects_extra_lines() {
117 use crate::{sign, KeyPairBox};
118
119 let password = b"password";
120 let keypair = KeyPairBox::generate(Some(password), None, None).unwrap();
121 let file = format!(
122 "{}extra\n",
123 sign(
124 Some(&keypair.public_key_box),
125 &keypair.secret_key_box,
126 Some(password),
127 "test".as_bytes(),
128 Some("trusted comment"),
129 Some("untrusted comment"),
130 )
131 .unwrap()
132 );
133
134 assert!(parse_signature(&file).is_err());
135}
136#[cfg(test)]
137#[test]
138fn test_parse_signature_rejects_control_char_comments() {
139 use crate::{sign, KeyPairBox};
140
141 let password = b"password";
142 let keypair = KeyPairBox::generate(Some(password), None, None).unwrap();
143 let file = sign(
144 Some(&keypair.public_key_box),
145 &keypair.secret_key_box,
146 Some(password),
147 "test".as_bytes(),
148 Some("trusted comment"),
149 Some("untrusted comment"),
150 )
151 .unwrap()
152 .to_string()
153 .replace(
154 "trusted comment: trusted comment",
155 "trusted comment: trusted\0comment",
156 );
157
158 assert!(parse_signature(&file).is_err());
159}
160impl Display for SignatureBox<'_> {
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 sig_format = vec![];
170 sig_format.extend_from_slice(&self.signature.sig_alg);
171 sig_format.extend_from_slice(&self.signature.key_id);
172 sig_format.extend_from_slice(&self.signature.sig.to_bytes());
173 let sig = encoder.encode(&sig_format);
174 s.push_str(&sig);
175 s.push('\n');
176 s.push_str("trusted comment: ");
177 if let Some(c) = self.trusted_comment {
178 s.push_str(c);
179 }
180 s.push('\n');
181 let global_sig = encoder.encode(self.signature.global_sig.to_bytes());
182 s.push_str(&global_sig);
183 s.push('\n');
184 write!(f, "{}", s)
185 }
186}
187
188impl<'s> SignatureBox<'s> {
189 pub(crate) fn new(
190 untrusted_comment: Option<&'s str>,
191 trusted_comment: Option<&'s str>,
192 signature: Signature,
193 ) -> Result<Self> {
194 validate_comment(untrusted_comment, ErrorKind::SignatureError)?;
195 validate_comment(trusted_comment, ErrorKind::SignatureError)?;
196 Ok(Self {
197 untrusted_comment,
198 trusted_comment,
199 signature,
200 })
201 }
202 pub fn is_prehashed(&self) -> bool {
203 self.signature.sig_alg == SIGALG_PREHASHED
204 }
205 pub fn untrusted_comment(&self) -> Option<&'s str> {
206 self.untrusted_comment
207 }
208 pub fn trusted_comment(&self) -> Option<&'s str> {
209 self.trusted_comment
210 }
211 pub fn key_id(&self) -> &[u8; KID_SIZE] {
212 &self.signature.key_id
213 }
214 #[allow(clippy::should_implement_trait)]
215 pub fn from_str(s: &str) -> Result<SignatureBox<'_>> {
216 parse_signature(s)
217 }
218}
219#[derive(Debug, Clone)]
220pub(crate) struct Signature {
221 pub sig_alg: [u8; ALG_SIZE],
222 pub key_id: [u8; KID_SIZE],
223 pub sig: ed25519::Signature,
224 pub global_sig: ed25519::Signature,
225}
226impl Signature {
227 pub fn new(
228 sig_alg: [u8; ALG_SIZE],
229 key_id: [u8; KID_SIZE],
230 sig: ed25519::Signature,
231 global_sig: ed25519::Signature,
232 ) -> Self {
233 Self {
234 sig_alg,
235 key_id,
236 sig,
237 global_sig,
238 }
239 }
240}