write key pem files by default when not otherwise provided.

This commit is contained in:
mbecker20
2025-10-02 02:32:13 -07:00
parent e2680d0942
commit 4feecb4b97
8 changed files with 140 additions and 64 deletions

View File

@@ -1,4 +1,4 @@
use std::{fs::read_to_string, path::PathBuf, sync::OnceLock};
use std::{path::PathBuf, sync::OnceLock};
use anyhow::Context;
use colored::Colorize;
@@ -16,24 +16,17 @@ use komodo_client::entities::{
},
logger::LogConfig,
};
use noise::key::{EncodedKeyPair, SpkiPublicKey};
use noise::key::{SpkiPublicKey, load_maybe_generate_private_key};
/// Should call in startup to ensure Core errors without valid private key.
pub fn core_private_key() -> &'static String {
static CORE_PRIVATE_KEY: OnceLock<String> = OnceLock::new();
CORE_PRIVATE_KEY.get_or_init(|| {
let config = core_config();
let Some(private_key) = config.private_key.as_ref() else {
return EncodedKeyPair::generate().unwrap().private;
};
if let Some(path) = private_key.strip_prefix("file:") {
read_to_string(path)
.with_context(|| {
format!("Failed to read private key at {path:?}")
})
.unwrap()
if let Some(path) = config.private_key.strip_prefix("file:") {
load_maybe_generate_private_key(path).unwrap()
} else {
private_key.clone()
config.private_key.clone()
}
})
}
@@ -62,7 +55,7 @@ pub fn periphery_public_keys() -> Option<&'static [SpkiPublicKey]> {
let maybe_pem = if let Some(path) =
public_key.strip_prefix("file:")
{
read_to_string(path)
std::fs::read_to_string(path)
.with_context(|| {
format!("Failed to read public key at {path:?}")
})
@@ -167,7 +160,7 @@ pub fn core_config() -> &'static CoreConfig {
CoreConfig {
// Secret things overridden with file
private_key: maybe_read_item_from_file(env.komodo_private_key_file, env.komodo_private_key)
.or(config.private_key),
.unwrap_or(config.private_key),
passkey: maybe_read_item_from_file(env.komodo_passkey_file, env.komodo_passkey)
.or(config.passkey),
jwt_secret: maybe_read_item_from_file(env.komodo_jwt_secret_file, env.komodo_jwt_secret).unwrap_or(config.jwt_secret),

View File

@@ -11,22 +11,19 @@ use komodo_client::entities::{
config::periphery::{CliArgs, Env, PeripheryConfig},
logger::{LogConfig, LogLevel},
};
use noise::key::{EncodedKeyPair, SpkiPublicKey};
use noise::key::{SpkiPublicKey, load_maybe_generate_private_key};
/// Should call in startup to ensure Periphery errors without valid private key.
pub fn periphery_private_key() -> &'static String {
static PERIPHERY_PRIVATE_KEY: OnceLock<String> = OnceLock::new();
PERIPHERY_PRIVATE_KEY.get_or_init(|| {
let config = periphery_config();
let Some(private_key) = config.private_key.as_ref() else {
return EncodedKeyPair::generate().unwrap().private;
};
let private_key = config.private_key.clone().unwrap_or(format!(
"file:{}/keys/periphery.key",
config.root_directory.display()
));
if let Some(path) = private_key.strip_prefix("file:") {
read_to_string(path)
.with_context(|| {
format!("Failed to read private key at {path:?}")
})
.unwrap()
load_maybe_generate_private_key(path).unwrap()
} else {
private_key.clone()
}

View File

@@ -327,15 +327,19 @@ pub struct CoreConfig {
#[serde(default)]
pub internet_interface: String,
/// Default private key to use with Noise handshake to authenticate with Periphery agents.
/// If not provided, will use randomly generated one on startup.
/// Private key to use with Noise handshake to authenticate with Periphery agents.
///
/// Supports openssl generated pem file, `openssl genpkey -algorithm X25519 -out private.key`.
/// To load from file, use `private_key = "file:/path/to/private.key"`
/// To load from file, use `private_key = "file:/path/to/private.key"`.
///
/// Note. The private key used can be overridden for individual Servers / Builders
#[serde(skip_serializing_if = "Option::is_none")]
pub private_key: Option<String>,
/// If a file is specified and does not exist, will try to generate one at the path
/// and use it going forward.
///
/// Note. The private key used can be overridden for individual Servers / Builders.
///
/// Default: file:/config/keys/core.key
#[serde(default = "default_private_key")]
pub private_key: String,
/// Default accepted public keys to allow Periphery to connect.
/// Core gains knowledge of the Periphery public key through the noise handshake.
@@ -679,6 +683,10 @@ fn default_core_bind_ip() -> String {
"[::]".to_string()
}
fn default_private_key() -> String {
String::from("file:/config/keys/core.key")
}
fn default_frontend_path() -> String {
"/app/frontend".to_string()
}
@@ -798,13 +806,11 @@ impl CoreConfig {
host: config.host,
port: config.port,
bind_ip: config.bind_ip,
private_key: self.private_key.as_ref().map(|private_key| {
if private_key.starts_with("file:") {
private_key.clone()
} else {
empty_or_redacted(private_key)
}
}),
private_key: if self.private_key.starts_with("file:") {
self.private_key.clone()
} else {
empty_or_redacted(&self.private_key)
},
periphery_public_keys: config.periphery_public_keys,
passkey: config.passkey.as_deref().map(empty_or_redacted),
timezone: config.timezone,

View File

@@ -212,10 +212,14 @@ pub struct Env {
#[derive(Debug, Clone, Deserialize)]
pub struct PeripheryConfig {
/// The private key used with noise handshake.
/// If not provided, uses randomly generated one on startup.
///
/// Supports openssl generated pem file, `openssl genpkey -algorithm X25519 -out private.key`.
/// To load from file, use `private_key = "file:/path/to/private.key"`
///
/// If a file is specified and does not exist, will try to generate one at the path
/// and use it going forward.
///
/// Default: ${root_directory}/keys/periphery.key
#[serde(skip_serializing_if = "Option::is_none")]
pub private_key: Option<String>,
/// Optionally pin a specific Core public key for additional trust.

View File

@@ -13,15 +13,19 @@ pub async fn handle(command: &KeyCommand) -> anyhow::Result<()> {
.context("Failed to generate key pair")?;
match format {
KeyOutputFormat::Standard => {
println!("\nPrivate Key: {}", keys.private.red().bold());
println!("Public Key: {}", keys.public.bold());
println!(
"\nPrivate Key: {}",
keys.private.as_str().red().bold()
);
println!("Public Key: {}", keys.public.as_str().bold());
}
KeyOutputFormat::Json => {
print_json(&keys.private, &keys.public)?
}
KeyOutputFormat::JsonPretty => {
print_json_pretty(&keys.private, &keys.public)?
print_json(keys.private.as_str(), keys.public.as_str())?
}
KeyOutputFormat::JsonPretty => print_json_pretty(
keys.private.as_str(),
keys.public.as_str(),
)?,
}
Ok(())

View File

@@ -1,3 +1,5 @@
use std::{path::PathBuf, str::FromStr};
use anyhow::Context;
use der::AnyRef;
@@ -21,9 +23,9 @@ fn algorithm() -> spki::AlgorithmIdentifier<AnyRef<'static>> {
pub struct EncodedKeyPair {
/// pkcs8 encoded private key
pub private: String,
pub private: Pkcs8PrivateKey,
/// spki encoded public key
pub public: String,
pub public: SpkiPublicKey,
}
impl EncodedKeyPair {
@@ -32,10 +34,31 @@ impl EncodedKeyPair {
let keypair = builder
.generate_keypair()
.context("Failed to generate keypair")?;
let private =
Pkcs8PrivateKey::from_raw_bytes(&keypair.private)?.into_inner();
let public =
SpkiPublicKey::from_raw_bytes(&keypair.public)?.into_inner();
let private = Pkcs8PrivateKey::from_raw_bytes(&keypair.private)?;
let public = SpkiPublicKey::from_raw_bytes(&keypair.public)?;
Ok(EncodedKeyPair { private, public })
}
}
pub fn load_maybe_generate_private_key(
path: &str,
) -> anyhow::Result<String> {
let path = path.as_ref();
let path = PathBuf::from_str(path)
.with_context(|| format!("Invalid private key path: {path:?}"))?;
if path
.try_exists()
.with_context(|| format!("Invalid private key path: {path:?}"))?
{
// Already exists, load it
std::fs::read_to_string(&path).with_context(|| {
format!("Failed to read private key at {path:?}")
})
} else {
// Generate and write pems to path
let keys = EncodedKeyPair::generate()?;
keys.private.write_pem(&path)?;
keys.public.write_pem(path.with_extension("pub"))?;
Ok(keys.private.into_inner())
}
}

View File

@@ -1,3 +1,5 @@
use std::path::Path;
use anyhow::{Context, anyhow};
use base64::{Engine as _, prelude::BASE64_STANDARD};
use der::{Decode as _, Encode as _, asn1::OctetStringRef};
@@ -26,6 +28,26 @@ impl Pkcs8PrivateKey {
self.0
}
pub fn as_pem(&self) -> String {
let private_key = &self.0;
format!(
r#"-----BEGIN PRIVATE KEY-----
{private_key}
-----END PRIVATE KEY-----
"#
)
}
pub fn write_pem<P: AsRef<Path>>(
&self,
path: P,
) -> anyhow::Result<()> {
let path = path.as_ref();
std::fs::write(path, self.as_pem()).with_context(|| {
format!("Failed to write private key pem to {path:?}")
})
}
pub fn from_raw_bytes(private_key: &[u8]) -> anyhow::Result<Self> {
if private_key.len() > 32 {
return Err(anyhow!(
@@ -65,6 +87,8 @@ impl Pkcs8PrivateKey {
Self::raw_bytes(self.0.as_bytes())
}
/// Converts pkcs8 base64 bytes
/// to raw private key
pub fn raw_bytes(
pkcs8_private_key: &[u8],
) -> anyhow::Result<Vec<u8>> {
@@ -74,21 +98,9 @@ impl Pkcs8PrivateKey {
Self::raw_bytes_after_decode(&decoded)
}
fn raw_bytes_after_decode(
decoded: &[u8],
) -> anyhow::Result<Vec<u8>> {
let pki = pkcs8::PrivateKeyInfo::from_der(decoded)
.map_err(anyhow::Error::msg)
.context("Failed to parse pki from der")?;
if pki.algorithm.oid != super::OID_X25519 {
return Err(anyhow!("Private key is not X25519"));
}
let octet = OctetStringRef::from_der(pki.private_key)
.map_err(anyhow::Error::msg)
.context("Failed to get octet string ref from private key")?;
Ok(octet.as_bytes().to_vec())
}
/// - For raw bytes: clones and returns
/// - For pkcs8 base64: converts and returns
/// - For pkcs8 base64 pem: unwraps and returns
pub fn maybe_raw_bytes(
maybe_pkcs8_private_key: &str,
) -> anyhow::Result<Vec<u8>> {
@@ -113,6 +125,21 @@ impl Pkcs8PrivateKey {
}
}
fn raw_bytes_after_decode(
decoded: &[u8],
) -> anyhow::Result<Vec<u8>> {
let pki = pkcs8::PrivateKeyInfo::from_der(decoded)
.map_err(anyhow::Error::msg)
.context("Failed to parse pki from der")?;
if pki.algorithm.oid != super::OID_X25519 {
return Err(anyhow!("Private key is not X25519"));
}
let octet = OctetStringRef::from_der(pki.private_key)
.map_err(anyhow::Error::msg)
.context("Failed to get octet string ref from private key")?;
Ok(octet.as_bytes().to_vec())
}
pub fn compute_public_key(
&self,
) -> anyhow::Result<super::public::SpkiPublicKey> {

View File

@@ -1,3 +1,5 @@
use std::path::Path;
use anyhow::{Context, anyhow};
use base64::{Engine as _, prelude::BASE64_STANDARD};
use der::{Decode as _, Encode as _, asn1::BitStringRef};
@@ -27,6 +29,26 @@ impl SpkiPublicKey {
self.0
}
pub fn as_pem(&self) -> String {
let public_key = &self.0;
format!(
r#"-----BEGIN PUBLIC KEY-----
{public_key}
-----END PUBLIC KEY-----
"#
)
}
pub fn write_pem<P: AsRef<Path>>(
&self,
path: P,
) -> anyhow::Result<()> {
let path = path.as_ref();
std::fs::write(path, self.as_pem()).with_context(|| {
format!("Failed to write private key pem to {path:?}")
})
}
/// Accepts pem rfc7468 (openssl) or base64 der (second line of rfc7468 pem).
pub fn from_maybe_pem(
public_key_maybe_pem: &str,