systemprompt_cli/commands/core/services/
signing.rs1use std::path::Path;
12
13use anyhow::{Context, Result, bail};
14use base64::Engine;
15use rand::{Rng, rng};
16use systemprompt_security::manifest_signing::{key_id_for_pubkey, pubkey_b64_from_seed};
17
18pub const SEED_BYTES: usize = 32;
19pub const ENV_PREFIX: &str = "env:";
20
21#[derive(Debug, Clone)]
22pub struct BundleSigningKey {
23 pub seed: [u8; SEED_BYTES],
24 pub public_key: String,
25 pub key_id: String,
26}
27
28impl BundleSigningKey {
29 #[must_use]
30 pub fn from_seed(seed: [u8; SEED_BYTES]) -> Self {
31 let public_key = pubkey_b64_from_seed(&seed);
32 let key_id = key_id_for_pubkey(&public_key);
33 Self {
34 seed,
35 public_key,
36 key_id,
37 }
38 }
39
40 #[must_use]
41 pub fn generate() -> Self {
42 let mut seed = [0u8; SEED_BYTES];
43 rng().fill_bytes(&mut seed);
44 Self::from_seed(seed)
45 }
46
47 #[must_use]
48 pub fn seed_b64(&self) -> String {
49 base64::engine::general_purpose::STANDARD.encode(self.seed)
50 }
51}
52
53pub fn load_signing_key(reference: &str) -> Result<BundleSigningKey> {
54 let encoded = if let Some(var) = reference.strip_prefix(ENV_PREFIX) {
55 std::env::var(var).with_context(|| format!("Signing key variable {var} is not set"))?
56 } else {
57 std::fs::read_to_string(Path::new(reference))
58 .with_context(|| format!("Failed to read signing key {reference}"))?
59 };
60 decode_seed(encoded.trim())
61}
62
63pub fn decode_seed(encoded: &str) -> Result<BundleSigningKey> {
64 let raw = base64::engine::general_purpose::STANDARD
65 .decode(encoded)
66 .context("Signing key is not valid base64")?;
67 let seed: [u8; SEED_BYTES] = raw.as_slice().try_into().map_or_else(
68 |_e| {
69 bail!(
70 "Signing key must decode to {SEED_BYTES} bytes, got {}",
71 raw.len()
72 )
73 },
74 Ok,
75 )?;
76 Ok(BundleSigningKey::from_seed(seed))
77}