homestar_runtime/settings/
pubkey_config.rs

1//! Pubkey configuration.
2
3use anyhow::{anyhow, Context};
4use clap::ValueEnum;
5use libp2p::{identity, identity::secp256k1};
6use rand::{Rng, SeedableRng};
7use sec1::{der::Decode, pkcs8::DecodePrivateKey};
8use serde::{Deserialize, Serialize};
9use serde_with::{base64::Base64, serde_as};
10use std::{
11    fmt::Display,
12    io::Read,
13    path::{Path, PathBuf},
14};
15use tracing::info;
16
17#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
18/// Configure how the Network keypair is generated or using an existing one
19pub enum PubkeyConfig {
20    /// A randomly generated key, intended primarily for testing
21    #[serde(rename = "random")]
22    Random,
23    /// Seed string should be a base64 encoded 32 bytes. This is used as the RNG seed to generate a key.
24    #[serde(rename = "random_seed")]
25    GenerateFromSeed(RNGSeed),
26    /// File path to a PEM encoded key
27    #[serde(rename = "existing")]
28    Existing(ExistingKeyPath),
29}
30
31/// Supported key types of homestar
32#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, ValueEnum)]
33pub enum KeyType {
34    /// Ed25519 key
35    #[default]
36    #[serde(rename = "ed25519")]
37    Ed25519,
38    /// Secp256k1 key
39    #[serde(rename = "secp256k1")]
40    Secp256k1,
41}
42
43impl Display for KeyType {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        match self {
46            KeyType::Ed25519 => f.write_str("Ed25519"),
47            KeyType::Secp256k1 => f.write_str("Secp256k1"),
48        }
49    }
50}
51
52/// Seed material for RNG generated keys
53#[serde_as]
54#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
55pub struct RNGSeed {
56    #[serde(default)]
57    key_type: KeyType,
58    #[serde_as(as = "Base64")]
59    seed: [u8; 32],
60}
61
62impl RNGSeed {
63    /// Create a new [RNGSeed]
64    pub fn new(key_type: KeyType, seed: [u8; 32]) -> Self {
65        Self { key_type, seed }
66    }
67}
68
69/// Info on where and what the Key file is
70#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
71pub struct ExistingKeyPath {
72    #[serde(default)]
73    key_type: KeyType,
74    path: PathBuf,
75}
76
77impl ExistingKeyPath {
78    /// Create a new [ExistingKeyPath]
79    pub fn new(key_type: KeyType, path: PathBuf) -> Self {
80        Self { key_type, path }
81    }
82}
83
84impl PubkeyConfig {
85    /// Produce a Keypair using the given configuration.
86    /// Calling this function will access the filesystem if configured to import a key.
87    pub(crate) fn keypair(&self) -> anyhow::Result<identity::Keypair> {
88        match self {
89            PubkeyConfig::Random => {
90                info!(
91                    subject = "pubkey_config.random",
92                    category = "pubkey_config",
93                    "generating random ed25519 key"
94                );
95                Ok(identity::Keypair::generate_ed25519())
96            }
97            PubkeyConfig::GenerateFromSeed(RNGSeed { key_type, seed }) => {
98                // seed RNG with supplied seed
99                let mut r = rand::prelude::StdRng::from_seed(*seed);
100                let mut new_key: [u8; 32] = r.gen();
101
102                match key_type {
103                    KeyType::Ed25519 => {
104                        info!(
105                            subject = "pubkey_config.random_seed.ed25519",
106                            category = "pubkey_config",
107                            "generating random ed25519 key from seed"
108                        );
109
110                        identity::Keypair::ed25519_from_bytes(new_key).map_err(|e| {
111                            anyhow!("failed to generate ed25519 key from random: {:?}", e)
112                        })
113                    }
114                    KeyType::Secp256k1 => {
115                        info!(
116                            subject = "pubkey_config.random_seed.secp256k1",
117                            category = "pubkey_config",
118                            "generating random secp256k1 key from seed"
119                        );
120
121                        let sk =
122                            secp256k1::SecretKey::try_from_bytes(&mut new_key).map_err(|e| {
123                                anyhow!("failed to generate secp256k1 key from random: {:?}", e)
124                            })?;
125                        let kp = secp256k1::Keypair::from(sk);
126                        Ok(identity::Keypair::from(kp))
127                    }
128                }
129            }
130            PubkeyConfig::Existing(ExistingKeyPath { key_type, path }) => {
131                let path = Path::new(&path);
132
133                let mut file = std::fs::File::open(path).context("unable to read key file")?;
134
135                let mut buf = Vec::new();
136                file.read_to_end(&mut buf)
137                    .context("unable to read bytes from file, is the file corrupted?")?;
138
139                match key_type {
140                    KeyType::Ed25519 => {
141                        info!(
142                            subject = "pubkey_config.path.ed25519",
143                            category = "pubkey_config",
144                            "importing ed25519 key from: {}",
145                            path.display()
146                        );
147
148                        String::from_utf8(buf.clone()).with_context(|| {
149                            "unable to read PEM, file contained invalid UTF-8 code points"
150                        })
151                        .and_then(|pem| ed25519_dalek::SigningKey::from_pkcs8_pem(&pem).with_context(|| "unable to deserialize ed25119 key from PEM"))
152                        .and_then(|pem| {
153                            let mut key = pem.to_bytes();
154
155                            identity::Keypair::ed25519_from_bytes(&mut key).with_context(|| "imported key material was invalid for ed25519")
156                        })
157                       .or_else(|_| {
158                            // parsing using ed25519_dalek failed, so try falling back to the older parsing strategy that attempts
159                            // to deserialize the key material as a single byte vec, with no parameters included
160                            const PEM_HEADER: &str = "PRIVATE KEY";
161
162                            // to parse keys as a vec of the key material, with no parameters included
163                            let (tag, mut key) = sec1::der::pem::decode_vec(&buf)
164                                .map_err(|e| anyhow!("key file must be PEM formatted: {:#?}", e))?;
165                            if tag != PEM_HEADER {
166                                return Err(anyhow!("imported key file had a header of '{tag}', expected '{PEM_HEADER}' for ed25519"));
167                            }
168
169                            identity::Keypair::ed25519_from_bytes(&mut key)
170                                                        .with_context(|| "imported key material was invalid for ed25519")
171                        })
172                    }
173                    KeyType::Secp256k1 => {
174                        info!(
175                            subject = "pubkey_config.path.secp256k1",
176                            category = "pubkey_config",
177                            "importing secp256k1 key from: {}",
178                            path.display()
179                        );
180
181                        let sk = match path.extension().and_then(|ext| ext.to_str()) {
182                            Some("der") => sec1::EcPrivateKey::from_der(buf.as_slice()).map_err(|e| anyhow!("failed to parse DER encoded secp256k1 key: {e:#?}")),
183                            Some("pem") => {
184                                Err(anyhow!("PEM encoded secp256k1 keys are unsupported at the moment. Please file an issue if you require this."))
185                            },
186                            _ => Err(anyhow!("please disambiguate file from either PEM or DER with a file extension."))
187                        }?;
188                        let kp = secp256k1::SecretKey::try_from_bytes(sk.private_key.to_vec())
189                            .map(secp256k1::Keypair::from)
190                            .map_err(|e| anyhow!("failed to import secp256k1 key: {:#?}", e))?;
191                        Ok(identity::Keypair::from(kp))
192                    }
193                }
194            }
195        }
196    }
197}