debian_control/
pgp.rs

1//! PGP signature parsing.
2
3/// Error during PGP signature parsing.
4#[derive(Debug, PartialEq, Eq)]
5pub enum Error {
6    /// Missing PGP signature.
7    MissingPgpSignature,
8
9    /// Payload missing in the signed message.
10    MissingPayload,
11
12    /// Truncated PGP signature.
13    TruncatedPgpSignature,
14
15    /// Junk after PGP signature.
16    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
32/// Strip a PGP signature from a signed message.
33///
34/// This function takes a signed message and returns the payload and the PGP signature.
35/// If the input is not a signed message, the function returns the input as the payload and `None`
36/// as the signature.
37///
38/// # Arguments
39/// * `input` - The signed message.
40///
41/// # Errors
42/// This function returns an error if the input is a signed message but the payload, PGP signature,
43/// or the PGP signature metadata is missing, or if there is junk after the PGP signature.
44/// The error indicates the reason for the failure.
45///
46/// # Returns
47/// A tuple containing the payload and the PGP signature, if present.
48///
49/// # Examples
50/// ```
51/// let input = "-----BEGIN PGP SIGNED MESSAGE-----
52/// Hash: SHA256
53///
54/// Hello, world!
55/// -----BEGIN PGP SIGNATURE-----
56/// iQIzBAEBCAAdFiEEpyNohvPMyq0Uiif4DphATThvodkFAmbJ6swACgkQDphATThv
57/// odkUiw//VDVOwHGRVxpvyIjSvH0AMQmANOvolJ5EoCu1I5UG2x98UPiMV5oTNv1r
58/// ...
59/// =olY7
60/// -----END PGP SIGNATURE-----
61/// ";
62/// let (output, signature) = debian_control::pgp::strip_pgp_signature(input).unwrap();
63/// assert_eq!(output, "Hello, world!\n");
64/// assert_eq!(signature.unwrap().len(), 136);
65/// ```
66pub 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    // Read the metadata
78    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}