1use std::fmt::Display;
2use std::vec;
3
4use base64::Engine;
5use ed25519_dalek::ed25519;
6
7use crate::errors::Result;
8use crate::{SError, ALG_SIZE, KID_SIZE, SIGALG_PREHASHED, SIG_SIZE};
9#[derive(Debug, Clone)]
18pub struct SignatureBox<'s> {
19 pub(crate) untrusted_comment: Option<&'s str>,
20 pub(crate) trusted_comment: Option<&'s str>,
21 pub(crate) signature: Signature,
22}
23fn parse_signature(s: &'_ str) -> Result<SignatureBox<'_>> {
24 let mut lines = s.lines();
25 let untrusted_comment = if let Some(c) = lines.next() {
26 if let Some(uc) = c.strip_prefix("untrusted comment: ") {
27 Some(uc)
28 } else {
29 return Err(SError::new(
30 crate::ErrorKind::SignatureError,
31 "missing untrusted comment",
32 ));
33 }
34 } else {
35 None
36 };
37 let sig = lines
38 .next()
39 .ok_or_else(|| SError::new(crate::ErrorKind::SignatureError, "missing signature"))?;
40 let decoder = base64::engine::general_purpose::STANDARD;
41 let sig_format = decoder
42 .decode(sig.as_bytes())
43 .map_err(|e| SError::new(crate::ErrorKind::SignatureError, e))?;
44 if sig_format.len() != ALG_SIZE + KID_SIZE + SIG_SIZE {
45 return Err(SError::new(
46 crate::ErrorKind::SignatureError,
47 "invalid signature length",
48 ));
49 }
50 let sig_alg = &sig_format[..ALG_SIZE];
51 let key_id = &sig_format[ALG_SIZE..ALG_SIZE + KID_SIZE];
52 let sig = &sig_format[ALG_SIZE + KID_SIZE..];
53 let trusted_comment = if let Some(c) = lines.next() {
54 if let Some(tc) = c.strip_prefix("trusted comment: ") {
55 Some(tc)
56 } else {
57 return Err(SError::new(
58 crate::ErrorKind::SignatureError,
59 "missing trusted comment",
60 ));
61 }
62 } else {
63 return Err(SError::new(
64 crate::ErrorKind::SignatureError,
65 "missing trusted comment",
66 ));
67 };
68 let global_sig = lines
69 .next()
70 .ok_or_else(|| SError::new(crate::ErrorKind::SignatureError, "missing global signature"))?;
71 let global_sig_format = decoder
72 .decode(global_sig.as_bytes())
73 .map_err(|e| SError::new(crate::ErrorKind::SignatureError, e))?;
74 if global_sig_format.len() != 64 {
75 return Err(SError::new(
76 crate::ErrorKind::SignatureError,
77 "invalid global signature length",
78 ));
79 }
80 Ok(SignatureBox::new(
81 untrusted_comment,
82 trusted_comment,
83 Signature::new(
84 sig_alg.try_into().unwrap(),
85 key_id.try_into().unwrap(),
86 sig.try_into().unwrap(),
87 ed25519::Signature::from_bytes(&global_sig_format.try_into().unwrap()),
88 ),
89 ))
90}
91
92#[cfg(test)]
93#[test]
94fn test_parse_signature() {
95 use crate::{sign, KeyPairBox};
96
97 let password = b"password";
98 let k = KeyPairBox::generate(Some(password), None, None).unwrap();
99 let file = sign(
100 Some(&k.public_key_box),
101 &k.secret_key_box,
102 Some(password),
103 "test".as_bytes(),
104 Some("trusted comment"),
105 Some("untrusted comment"),
106 )
107 .unwrap()
108 .to_string();
109 let sig = parse_signature(&file).unwrap();
110 assert_eq!(file, sig.to_string());
111}
112fn sanitize_comment(comment: Option<&str>) -> Option<String> {
113 comment.map(|c| {
114 c.chars()
115 .filter(|c| !c.is_control())
116 .collect::<String>()
117 })
118}
119impl Display for SignatureBox<'_> {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 let mut s = String::new();
122 s.push_str("untrusted comment: ");
123 if let Some(c) = sanitize_comment(self.untrusted_comment) {
124 s.push_str(&c);
125 }
126 s.push('\n');
127 let encoder = base64::engine::general_purpose::STANDARD;
128 let mut sig_format = vec![];
129 sig_format.extend_from_slice(&self.signature.sig_alg);
130 sig_format.extend_from_slice(&self.signature.key_id);
131 sig_format.extend_from_slice(&self.signature.sig.to_bytes());
132 let sig = encoder.encode(&sig_format);
133 s.push_str(&sig);
134 s.push('\n');
135 s.push_str("trusted comment: ");
136 if let Some(c) = sanitize_comment(self.trusted_comment) {
137 s.push_str(&c);
138 }
139 s.push('\n');
140 let global_sig = encoder.encode(self.signature.global_sig.to_bytes());
141 s.push_str(&global_sig);
142 s.push('\n');
143 write!(f, "{}", s)
144 }
145}
146
147impl<'s> SignatureBox<'s> {
148 pub(crate) fn new(
149 untrusted_comment: Option<&'s str>,
150 trusted_comment: Option<&'s str>,
151 signature: Signature,
152 ) -> Self {
153 Self {
154 untrusted_comment,
155 trusted_comment,
156 signature,
157 }
158 }
159 pub fn is_prehashed(&self) -> bool {
160 self.signature.sig_alg == SIGALG_PREHASHED
161 }
162 pub fn untrusted_comment(&self) -> Option<&'s str> {
163 self.untrusted_comment
164 }
165 pub fn trusted_comment(&self) -> Option<&'s str> {
166 self.trusted_comment
167 }
168 pub fn key_id(&self) -> &[u8; KID_SIZE] {
169 &self.signature.key_id
170 }
171 #[allow(clippy::should_implement_trait)]
172 pub fn from_str(s: &str) -> Result<SignatureBox<'_>> {
173 parse_signature(s)
174 }
175}
176#[derive(Debug, Clone)]
177pub(crate) struct Signature {
178 pub sig_alg: [u8; ALG_SIZE],
179 pub key_id: [u8; KID_SIZE],
180 pub sig: ed25519::Signature,
181 pub global_sig: ed25519::Signature,
182}
183impl Signature {
184 pub fn new(
185 sig_alg: [u8; ALG_SIZE],
186 key_id: [u8; KID_SIZE],
187 sig: ed25519::Signature,
188 global_sig: ed25519::Signature,
189 ) -> Self {
190 Self {
191 sig_alg,
192 key_id,
193 sig,
194 global_sig,
195 }
196 }
197}