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