1#[derive(Debug, PartialEq, Eq)]
5pub enum Error {
6 MissingPgpSignature,
8
9 MissingPayload,
11
12 TruncatedPgpSignature,
14
15 JunkAfterPgpSignature,
17}
18
19impl std::fmt::Display for Error {
20 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
21 match self {
22 Error::MissingPgpSignature => write!(f, "missing PGP signature"),
23 Error::TruncatedPgpSignature => write!(f, "truncated PGP signature"),
24 Error::JunkAfterPgpSignature => write!(f, "junk after PGP signature"),
25 Error::MissingPayload => write!(f, "missing payload"),
26 }
27 }
28}
29
30impl std::error::Error for Error {}
31
32pub fn strip_pgp_signature(input: &str) -> Result<(String, Option<String>), Error> {
67 let mut lines = input.lines();
68 let first_line = if let Some(line) = lines.next() {
69 line
70 } else {
71 return Ok((input.to_string(), None));
72 };
73 if first_line != "-----BEGIN PGP SIGNED MESSAGE-----" {
74 return Ok((input.to_string(), None));
75 }
76
77 let mut metadata = String::new();
79 loop {
80 let line = if let Some(line) = lines.next() {
81 line
82 } else {
83 return Err(Error::MissingPayload);
84 };
85 if line.is_empty() {
86 break;
87 }
88 metadata.push_str(line);
89 metadata.push('\n');
90 }
91
92 let mut payload = String::new();
93 loop {
94 let line = if let Some(line) = lines.next() {
95 line
96 } else {
97 return Err(Error::MissingPgpSignature);
98 };
99 if line == "-----BEGIN PGP SIGNATURE-----" {
100 break;
101 }
102 payload.push_str(line);
103 payload.push('\n');
104 }
105
106 let mut signature = String::new();
107 loop {
108 let line = if let Some(line) = lines.next() {
109 line
110 } else {
111 return Err(Error::TruncatedPgpSignature);
112 };
113 if line == "-----END PGP SIGNATURE-----" {
114 break;
115 }
116 signature.push_str(line);
117 }
118
119 if let Some(_line) = lines.next() {
120 return Err(Error::JunkAfterPgpSignature);
121 }
122
123 Ok((payload, Some(signature)))
124}
125
126#[cfg(test)]
127mod tests {
128 #[test]
129 fn test_strip_pgp_wrapper() {
130 let input = include_str!("testdata/InRelease");
131
132 let (output, signature) = super::strip_pgp_signature(input).unwrap();
133
134 assert_eq!(
135 output,
136 r###"Origin: Debian
137Label: Debian
138Suite: experimental
139Codename: rc-buggy
140Changelogs: https://metadata.ftp-master.debian.org/changelogs/@CHANGEPATH@_changelog
141Date: Sat, 24 Aug 2024 14:13:49 UTC
142Valid-Until: Sat, 31 Aug 2024 14:13:49 UTC
143NotAutomatic: yes
144Acquire-By-Hash: yes
145No-Support-for-Architecture-all: Packages
146Architectures: all amd64 arm64 armel armhf i386 mips64el ppc64el riscv64 s390x
147Components: main contrib non-free-firmware non-free
148Description: Experimental packages - not released; use at your own risk.
149"###
150 );
151
152 assert_eq!(
153 signature.as_deref(),
154 Some(
155 r###"iQIzBAEBCAAdFiEEpyNohvPMyq0Uiif4DphATThvodkFAmbJ6swACgkQDphATThv
156odkUiw//VDVOwHGRVxpvyIjSvH0AMQmANOvolJ5EoCu1I5UG2x98UPiMV5oTNv1r
157B79A3nb+FL2toeuHUJBN3G1WNg6xeH0vD43hGcxhCgVn6NADogv8pBEpyynn1qC0
158iketp6kEiHvGMpEj4JqOUEcq2Mafq2TTf9zEqYuTr8NqL9hC/pG8YqPKT3rhPdc3
159/D4/0dTT7L+wqLgVTjjNFNcmKU1ywvaWLF5b0VktZ1W6xIqnZYfHyP0iMqolrGqF
160+NG+igpsMuLI6JtoqoE+yKtWlaQi7pY7VB+OFroywNxEobzPgwdqz0r8pdJq8S4V
161CMXJqoY18KHdVd4qbU9yGr6qkqopHdMMcpvV9X1UG5xDKUb2OrdbBYttKFGuIuuM
162S6ZzM+26bztVXLzSra/w7gn7Qm1GluT+EncYrleAAgUvruCRrLFptDpHMAuKWKs5
163OyNLh1ZUe/TrkmYGhehsVEBNmG+/HnzS7VKzLfpANHLAXthoEF9Lzqe0lPETa9NZ
164rSF/EfQwh8omsaBDfighU46fZJwKGSWOIz69jXrQ6YV9hBI/frUDHQUkMLBwjnVo
1658hvr0s6/8hHRwNlLRW3XQuwL+wiz0qyk6u6RRudglqSyN1FwIAtTsGkERWN82au2
166DY6KLpnfN7/0bIueDUWCP40Dib+eW5Y0/Z536WhNbp8C/OIKeVyJAjMEAQEIAB0W
167IQRMtQGQIHtHWKP3Onlu0Oe4JkPhMQUCZsnq6QAKCRBu0Oe4JkPhMUJsD/0ZTGIM
168oI9bzhP6NadhiNNruxLQfq/+fVx/oJbyOJy4IaYPOE0JVeqzZv/wFL/XOVXw6Gg2
169V/SHe0cT+iuwdKd+8oMEYaOHQUeU8RhAguypeTdizZef3YjIL+2n4v0mLeq/jMHO
170a6Hyd09eUQrHedmcgViwQYOX/9/oqls0j3OGtyx1gmpIsmCxJtqsWNsXEcBLaNlm
171xSAp5YYa5USenFAph4VlR2sG+VdJrG/wtCj8TuDtJCA4tOML3JB5zwgnzfpLMVU6
172l+WFKkSzl0f/dlMUYRtRoU9ccpWpyajMs968QsOp0lKLZ5Kq98fSXqOzKriDimpv
1734WSmlLRptRgKL0J/Nc1eYRVEPnu+tBsitLdip52SLrqYcbCOErtxOLMIIbbC2HiR
174Q0lYYgky2TwO8bbCWhTyQIznldnSRhNE1STf5bctphNeWQE6zRFmMGyHh9pQYVNF
175KkmCbzHcv6EbUOp7Q7c5D/mijN8On/h9TEYU6EbbrQ1AEc+IulXukzlaLCMKJ0Tx
176XqsogWqW/nbOxTdudMn+qjd7gVsLtNIDKA42Csyac5Hwl9YDqgicyOMGBY88gocV
1778fDXnyUhX5Es35AgO25Sh8CbISC29479o4/MdZXCGMIJEocjPx46Dy+hP1sIcFyp
178KYQwHDLf3TLHWF9z0lvGFYSAq1H8gOwchDISGA==
179=olY7
180"###
181 .replace('\n', "")
182 .as_ref()
183 )
184 );
185 }
186
187 #[test]
188 fn test_strip_pgp_no_pgp_signature() {
189 let input = "Hello, world!";
190 let (output, signature) = super::strip_pgp_signature(input).unwrap();
191 assert_eq!(output, input);
192 assert_eq!(signature, None);
193 }
194
195 #[test]
196 fn test_strip_pgp_missing_payload() {
197 let input = r###"-----BEGIN PGP SIGNED MESSAGE-----
198Hash: SHA256
199"###;
200 let err = super::strip_pgp_signature(input).unwrap_err();
201 assert_eq!(err, super::Error::MissingPayload);
202 }
203
204 #[test]
205 fn test_strip_pgp_missing_pgp_signature() {
206 let input = r###"-----BEGIN PGP SIGNED MESSAGE-----
207Hash: SHA256
208
209Hello, world!
210"###;
211 let err = super::strip_pgp_signature(input).unwrap_err();
212 assert_eq!(err, super::Error::MissingPgpSignature);
213 }
214
215 #[test]
216 fn test_strip_pgp_truncated_pgp_signature() {
217 let input = r###"-----BEGIN PGP SIGNED MESSAGE-----
218Hash: SHA256
219
220Hello, world!
221
222-----BEGIN PGP SIGNATURE-----
223B79A3nb+FL2toeuHUJBN3G1WNg6xeH0vD43hGcxhCgVn6NADogv8pBEpyynn1qC0
224"###;
225 let err = super::strip_pgp_signature(input).unwrap_err();
226 assert_eq!(err, super::Error::TruncatedPgpSignature);
227 }
228
229 #[test]
230 fn test_strip_pgp_junk_after_pgp_signature() {
231 let input = r###"-----BEGIN PGP SIGNED MESSAGE-----
232Hash: SHA256
233
234Hello, world!
235
236-----BEGIN PGP SIGNATURE-----
237B79A3nb+FL2toeuHUJBN3G1WNg6xeH0vD43hGcxhCgVn6NADogv8pBEpyynn1qC0
238-----END PGP SIGNATURE-----
239Junk after PGP signature
240"###;
241 let err = super::strip_pgp_signature(input).unwrap_err();
242 assert_eq!(err, super::Error::JunkAfterPgpSignature);
243 }
244}