Skip to main content

nichlink_plugin_host/
verifier.rs

1//! Ed25519 verification at the plugin execution boundary.
2//! 插件执行边界上的 Ed25519 验证。
3
4use ed25519_dalek::{Signature, Verifier, VerifyingKey};
5use nichlink_run_method::{PluginManifest, PluginSignatureVerifier};
6
7/// One public key the host will trust, named by its SHA-256 fingerprint.
8/// 宿主愿意信任的一个公钥,以它的 SHA-256 指纹命名。
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub struct TrustedPublicKey {
11    /// SHA-256 hex of `bytes`; a key whose bytes do not hash to it is rejected.
12    /// `bytes` 的 SHA-256 十六进制;字节哈希对不上的公钥会被拒绝。
13    pub fingerprint: &'static str,
14    /// Raw 32-byte Ed25519 public key.
15    /// 32 字节的 Ed25519 原始公钥。
16    pub bytes: [u8; 32],
17}
18
19impl TrustedPublicKey {
20    /// Pair a fingerprint with the key bytes it must name.
21    /// 把指纹与它必须指称的公钥字节配对。
22    pub const fn new(fingerprint: &'static str, bytes: [u8; 32]) -> Self {
23        Self { fingerprint, bytes }
24    }
25}
26
27/// Verifies plugin signatures against a fixed set of trusted public keys.
28/// 用一组固定可信公钥验证插件签名。
29#[derive(Clone, Copy, Debug)]
30pub struct Ed25519Verifier {
31    keys: &'static [TrustedPublicKey],
32}
33
34impl Ed25519Verifier {
35    /// Trust exactly the listed keys for the lifetime of the verifier.
36    /// 在验证器生命周期内只信任列出的公钥。
37    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    /// Check `manifest.signature` against the canonical `payload`.
54    /// 用给定的规范化 `payload` 校验 `manifest.signature`。
55    ///
56    /// The payload is the message the kernel built for the artifact — manifest
57    /// fields, the registration that travels with the bytes, and the bytes
58    /// themselves — so this verifier never reassembles it and cannot drop a
59    /// field the kernel added.
60    /// 载荷是内核为工件构造的消息——manifest 字段、随字节同行的注册声明,以及字节本身——因此
61    /// 本验证器不自行拼装消息,也不会丢掉内核加入的字段。
62    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        // The host never rebuilds this message: it verifies whatever the kernel
94        // handed it, which is what makes the registration part of the signature.
95        // 宿主从不重建这条消息:它验证内核交给它的字节,注册声明因此进入签名覆盖范围。
96        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}