nichlink_plugin_host/
verifier.rs1use ed25519_dalek::{Signature, Verifier, VerifyingKey};
5use nichlink_run_method::{PluginManifest, PluginSignatureVerifier};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub struct TrustedPublicKey {
11 pub fingerprint: &'static str,
14 pub bytes: [u8; 32],
17}
18
19impl TrustedPublicKey {
20 pub const fn new(fingerprint: &'static str, bytes: [u8; 32]) -> Self {
23 Self { fingerprint, bytes }
24 }
25}
26
27#[derive(Clone, Copy, Debug)]
30pub struct Ed25519Verifier {
31 keys: &'static [TrustedPublicKey],
32}
33
34impl Ed25519Verifier {
35 pub const fn new(keys: &'static [TrustedPublicKey]) -> Self {
38 Self { keys }
39 }
40
41 fn key(&self, fingerprint: &str) -> Option<VerifyingKey> {
42 self.keys
43 .iter()
44 .find(|key| key.fingerprint.eq_ignore_ascii_case(fingerprint))
45 .filter(|key| {
46 nichlink_run_method::sha256_hex(&key.bytes).eq_ignore_ascii_case(key.fingerprint)
47 })
48 .and_then(|key| VerifyingKey::from_bytes(&key.bytes).ok())
49 }
50}
51
52impl PluginSignatureVerifier for Ed25519Verifier {
53 fn verify(&self, manifest: PluginManifest, payload: &[u8], fingerprint: &str) -> bool {
63 let Some(key) = self.key(fingerprint) else {
64 return false;
65 };
66 let Some(signature) = manifest.signature.and_then(decode_signature) else {
67 return false;
68 };
69 key.verify(payload, &signature).is_ok()
70 }
71}
72
73fn decode_signature(value: &str) -> Option<Signature> {
74 if value.len() != 128 {
75 return None;
76 }
77 let bytes: [u8; 64] = nichlink_run_method::hex_decode(value)?.try_into().ok()?;
78 Some(Signature::from_bytes(&bytes))
79}
80
81#[cfg(test)]
82mod tests {
83 use ed25519_dalek::{Signer, SigningKey};
84 use nichlink_run_method::{FrameworkId, PluginMode, PluginSource, sha256_hex};
85
86 use super::*;
87
88 #[test]
89 fn verifies_a_signature_over_the_payload_the_kernel_built() {
90 let signing = SigningKey::from_bytes(&[7; 32]);
91 let verifying = signing.verifying_key();
92 let fingerprint = Box::leak(sha256_hex(verifying.as_bytes()).into_boxed_str());
93 let payload = b"manifest+registration+bytes";
97 let encoded = signing
98 .sign(payload)
99 .to_bytes()
100 .iter()
101 .map(|byte| format!("{byte:02x}"))
102 .collect::<String>();
103 let key = TrustedPublicKey::new(fingerprint, verifying.to_bytes());
104 let verifier = Ed25519Verifier::new(Box::leak(Box::new([key])));
105
106 assert!(verifier.verify(
107 manifest(Some(Box::leak(encoded.into_boxed_str())), fingerprint),
108 payload,
109 fingerprint,
110 ));
111 assert!(!verifier.verify(manifest(Some("bad"), fingerprint), payload, fingerprint));
112 }
113
114 fn manifest(signature: Option<&'static str>, fingerprint: &'static str) -> PluginManifest {
115 PluginManifest {
116 name: "fixture",
117 crate_name: "fixture",
118 version: "1.0.0",
119 framework: FrameworkId::new("nichlink.default"),
120 source: PluginSource::Official,
121 mode: PluginMode::Extension,
122 checksum: "sha256:ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
123 signature,
124 public_key_fingerprint: Some(fingerprint),
125 revocation_list: Some("official-1"),
126 }
127 }
128}