alpm_lint/lint_rules/source_info/
openpgp_key_id.rs

1//! Ensures that [OpenPGP Key IDs] are not used in [SRCINFO] data.
2//!
3//! [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
4//! [OpenPGP Key IDs]: https://openpgp.dev/book/glossary.html#term-Key-ID
5
6use std::collections::BTreeMap;
7
8use documented::Documented;
9
10use crate::{
11    internal_prelude::*,
12    issue::SourceInfoIssue,
13    lint_rules::source_info::source_info_from_resource,
14};
15
16/// # What it does
17///
18/// Ensure that no [OpenPGP Key ID] is used to authenticate and verify upstream artifacts.
19///
20/// # Why is this bad?
21///
22/// An [OpenPGP certificate] can be used to verify and authenticate upstream sources.
23/// In [PKGBUILD] and [SRCINFO] files these certificates are identified using an ID.
24/// This allows the retrieval of matching certificates from remote resources (e.g. Web Key Directory
25/// or OpenPGP keyservers).
26///
27/// An [OpenPGP Key ID] is a short identifier that can be used to identify an [OpenPGP certificate].
28/// However, its uniqueness cannot be guaranteed and thus it does not guard against collision.
29///
30/// If an [OpenPGP certificate] cannot be uniquely identified:
31///
32/// - an arbitrary certificate may have a matching [OpenPGP Key ID] and it would not be possible to
33///   use it for authentication and verification of the particular upstream sources.
34/// - sophisticated attackers may be able to craft a certificate with a matching [OpenPGP Key ID]
35///   and swap upstream sources and digital signatures with malicious ones.
36///
37/// Only an [OpenPGP fingerprint] meaningfully guards against collision and should always be used
38/// instead of an [OpenPGP Key ID] to uniquely identify an [OpenPGP certificate].
39///
40/// # Example
41///
42/// ```ini,ignore
43/// pkgbase = test
44///     pkgver = 1.0.0
45///     pkgrel = 1
46///     arch = x86_64
47///     validpgpkeys = 2F2670AC164DB36F
48/// ```
49///
50/// Use instead:
51///
52/// ```ini,ignore
53/// pkgbase = test
54///     pkgver = 1.0.0
55///     pkgrel = 1
56///     arch = x86_64
57///     validpgpkeys = 4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E
58/// ```
59///
60/// [PKGBUILD]: https://man.archlinux.org/man/PKGBUILD.5
61/// [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
62/// [OpenPGP Key ID]: https://openpgp.dev/book/glossary.html#term-Key-ID
63/// [OpenPGP certificate]: https://openpgp.dev/book/certificates.html
64/// [OpenPGP fingerprint]: https://openpgp.dev/book/certificates.html#fingerprint
65#[derive(Clone, Debug, Documented)]
66pub struct OpenPGPKeyId {}
67
68impl OpenPGPKeyId {
69    /// Create a new, boxed instance of [`OpenPGPKeyId`].
70    pub fn new_boxed(_: &LintRuleConfiguration) -> Box<dyn LintRule> {
71        Box::new(OpenPGPKeyId {})
72    }
73}
74
75impl LintRule for OpenPGPKeyId {
76    fn name(&self) -> &'static str {
77        "openpgp_key_id"
78    }
79
80    fn scope(&self) -> LintScope {
81        LintScope::SourceInfo
82    }
83
84    fn level(&self) -> Level {
85        crate::Level::Deny
86    }
87
88    fn documentation(&self) -> String {
89        OpenPGPKeyId::DOCS.into()
90    }
91
92    fn help_text(&self) -> String {
93        r#"OpenPGP Key IDs are not safe and must not be used for authentication and verification.
94
95Key IDs are short identifiers (8 or 16 hex characters) that are not guaranteed to be unique.
96Use 40-character long OpenPGP fingerprints instead to prevent collision attacks.
97"#
98        .into()
99    }
100
101    fn run(&self, resources: &Resources, issues: &mut Vec<LintIssue>) -> Result<(), Error> {
102        // Extract the SourceInfo from the given resources.
103        let source_info = source_info_from_resource(resources, self.scoped_name())?;
104
105        // Check PGP identifiers to see if any are key IDs (not fingerprints)
106        for identifier in &source_info.base.pgp_fingerprints {
107            if matches!(identifier, alpm_types::OpenPGPIdentifier::OpenPGPKeyId(_)) {
108                issues.push(LintIssue::from_rule(
109                    self,
110                    SourceInfoIssue::BaseField {
111                        field_name: "validpgpkeys".to_string(),
112                        value: identifier.to_string(),
113                        context: "OpenPGP Key IDs are not allowed".to_string(),
114                        architecture: None,
115                    }
116                    .into(),
117                ));
118            }
119        }
120
121        Ok(())
122    }
123
124    fn extra_links(&self) -> Option<BTreeMap<String, String>> {
125        let mut links = BTreeMap::new();
126        links.insert(
127            "SRCINFO specification".to_string(),
128            "https://alpm.archlinux.page/specifications/SRCINFO.5.html".to_string(),
129        );
130        links.insert(
131            "OpenPGP Key ID - OpenPGP for application developers".to_string(),
132            "https://openpgp.dev/book/glossary.html#term-Key-ID".to_string(),
133        );
134        links.insert(
135            "OpenPGP certificate - OpenPGP for application developers".to_string(),
136            "https://openpgp.dev/book/certificates.html".to_string(),
137        );
138        links.insert(
139            "OpenPGP fingerprint - OpenPGP for application developers".to_string(),
140            "https://openpgp.dev/book/certificates.html#fingerprint".to_string(),
141        );
142
143        Some(links)
144    }
145}