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}