From 79d7561d93881b1a7d9d2627cf750ada0cf3e201 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 26 Jan 2022 18:29:11 +0100 Subject: [PATCH] Allow loading private keys from files --- crates/cli/src/server.rs | 1 + crates/config/src/oauth2.rs | 118 +++++++++++++++++++++++++----------- 2 files changed, 84 insertions(+), 35 deletions(-) diff --git a/crates/cli/src/server.rs b/crates/cli/src/server.rs index e9b33206..6386ebb1 100644 --- a/crates/cli/src/server.rs +++ b/crates/cli/src/server.rs @@ -253,6 +253,7 @@ impl ServerCommand { let key_store = config .oauth2 .key_store() + .await .context("could not import keys from config")?; // Wrap the key store in an Arc let key_store = Arc::new(key_store); diff --git a/crates/config/src/oauth2.rs b/crates/config/src/oauth2.rs index 6ccc13d2..cdd313cd 100644 --- a/crates/config/src/oauth2.rs +++ b/crates/config/src/oauth2.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::path::PathBuf; + use anyhow::Context; use async_trait::async_trait; use mas_jose::{JsonWebKeySet, StaticJwksStore, StaticKeystore}; @@ -24,7 +26,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use thiserror::Error; -use tokio::task; +use tokio::{fs::File, io::AsyncReadExt, task}; use tracing::info; use url::Url; @@ -37,10 +39,18 @@ pub enum KeyType { Ecdsa, } +#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub enum KeyOrPath { + Key(String), + Path(PathBuf), +} + #[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)] pub struct KeyConfig { r#type: KeyType, - key: String, + #[serde(flatten)] + key: KeyOrPath, } #[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)] @@ -140,20 +150,52 @@ impl OAuth2Config { .expect("could not build discovery url") } - pub fn key_store(&self) -> anyhow::Result { + pub async fn key_store(&self) -> anyhow::Result { let mut store = StaticKeystore::new(); - for key in &self.keys { - match key.r#type { + for item in &self.keys { + // Read the key either embedded in the config file or on disk + let mut buf = Vec::new(); + let (key_as_bytes, key_as_str) = match &item.key { + KeyOrPath::Key(key) => (key.as_bytes(), Some(key.as_str())), + KeyOrPath::Path(path) => { + let mut file = File::open(path).await?; + file.read_to_end(&mut buf).await?; + + (&buf[..], std::str::from_utf8(&buf).ok()) + } + }; + + match item.r#type { + // TODO: errors are not well carried here KeyType::Ecdsa => { - let key = p256::SecretKey::from_pkcs1_pem(&key.key) - .or_else(|_| p256::SecretKey::from_pkcs8_pem(&key.key)) - .or_else(|_| p256::SecretKey::from_sec1_pem(&key.key))?; + // First try to read it as DER from the bytes + let mut key = p256::SecretKey::from_pkcs1_der(key_as_bytes) + .or_else(|_| p256::SecretKey::from_pkcs8_der(key_as_bytes)) + .or_else(|_| p256::SecretKey::from_sec1_der(key_as_bytes)); + + // If the file was a valid string, try reading it as PEM + if let Some(key_as_str) = key_as_str { + key = key + .or_else(|_| p256::SecretKey::from_pkcs1_pem(key_as_str)) + .or_else(|_| p256::SecretKey::from_pkcs8_pem(key_as_str)) + .or_else(|_| p256::SecretKey::from_sec1_pem(key_as_str)); + } + + let key = key?; store.add_ecdsa_key(key.into())?; } KeyType::Rsa => { - let key = rsa::RsaPrivateKey::from_pkcs1_pem(&key.key) - .or_else(|_| rsa::RsaPrivateKey::from_pkcs8_pem(&key.key))?; + let mut key = rsa::RsaPrivateKey::from_pkcs1_der(key_as_bytes) + .or_else(|_| rsa::RsaPrivateKey::from_pkcs8_der(key_as_bytes)); + + if let Some(key_as_str) = key_as_str { + key = key + .or_else(|_| rsa::RsaPrivateKey::from_pkcs1_pem(key_as_str)) + .or_else(|_| rsa::RsaPrivateKey::from_pkcs8_pem(key_as_str)); + } + + let key = key?; store.add_rsa_key(key)?; } } @@ -186,9 +228,11 @@ impl ConfigurationSection<'_> for OAuth2Config { .context("could not join blocking task")??; let rsa_key = KeyConfig { r#type: KeyType::Rsa, - key: rsa_key - .to_pkcs1_pem(pem_rfc7468::LineEnding::LF)? - .to_string(), + key: KeyOrPath::Key( + rsa_key + .to_pkcs1_pem(pem_rfc7468::LineEnding::LF)? + .to_string(), + ), }; let span = tracing::info_span!("ecdsa"); @@ -203,7 +247,7 @@ impl ConfigurationSection<'_> for OAuth2Config { .context("could not join blocking task")?; let ecdsa_key = KeyConfig { r#type: KeyType::Ecdsa, - key: ecdsa_key.to_pem(pem_rfc7468::LineEnding::LF)?.to_string(), + key: KeyOrPath::Key(ecdsa_key.to_pem(pem_rfc7468::LineEnding::LF)?.to_string()), }; Ok(Self { @@ -216,30 +260,34 @@ impl ConfigurationSection<'_> for OAuth2Config { fn test() -> Self { let rsa_key = KeyConfig { r#type: KeyType::Rsa, - key: indoc::indoc! {r#" - -----BEGIN PRIVATE KEY----- - MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAymS2RkeIZo7pUeEN - QUGCG4GLJru5jzxomO9jiNr5D/oRcerhpQVc9aCpBfAAg4l4a1SmYdBzWqX0X5pU - scgTtQIDAQABAkEArNIMlrxUK4bSklkCcXtXdtdKE9vuWfGyOw0GyAB69fkEUBxh - 3j65u+u3ZmW+bpMWHgp1FtdobE9nGwb2VBTWAQIhAOyU1jiUEkrwKK004+6b5QRE - vC9UI2vDWy5vioMNx5Y1AiEA2wGAJ6ETF8FF2Vd+kZlkKK7J0em9cl0gbJDsWIEw - N4ECIEyWYkMurD1WQdTQqnk0Po+DMOihdFYOiBYgRdbnPxWBAiEAmtd0xJAd7622 - tPQniMnrBtiN2NxqFXHCev/8Gpc8gAECIBcaPcF59qVeRmYrfqzKBxFm7LmTwlAl - Gh7BNzCeN+D6 - -----END PRIVATE KEY----- - "#} - .to_string(), + key: KeyOrPath::Key( + indoc::indoc! {r#" + -----BEGIN PRIVATE KEY----- + MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAymS2RkeIZo7pUeEN + QUGCG4GLJru5jzxomO9jiNr5D/oRcerhpQVc9aCpBfAAg4l4a1SmYdBzWqX0X5pU + scgTtQIDAQABAkEArNIMlrxUK4bSklkCcXtXdtdKE9vuWfGyOw0GyAB69fkEUBxh + 3j65u+u3ZmW+bpMWHgp1FtdobE9nGwb2VBTWAQIhAOyU1jiUEkrwKK004+6b5QRE + vC9UI2vDWy5vioMNx5Y1AiEA2wGAJ6ETF8FF2Vd+kZlkKK7J0em9cl0gbJDsWIEw + N4ECIEyWYkMurD1WQdTQqnk0Po+DMOihdFYOiBYgRdbnPxWBAiEAmtd0xJAd7622 + tPQniMnrBtiN2NxqFXHCev/8Gpc8gAECIBcaPcF59qVeRmYrfqzKBxFm7LmTwlAl + Gh7BNzCeN+D6 + -----END PRIVATE KEY----- + "#} + .to_string(), + ), }; let ecdsa_key = KeyConfig { r#type: KeyType::Ecdsa, - key: indoc::indoc! {r#" - -----BEGIN PRIVATE KEY----- - MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgqfn5mYO/5Qq/wOOiWgHA - NaiDiepgUJ2GI5eq2V8D8nahRANCAARMK9aKUd/H28qaU+0qvS6bSJItzAge1VHn - OhBAAUVci1RpmUA+KdCL5sw9nadAEiONeiGr+28RYHZmlB9qXnjC - -----END PRIVATE KEY----- - "#} - .to_string(), + key: KeyOrPath::Key( + indoc::indoc! {r#" + -----BEGIN PRIVATE KEY----- + MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgqfn5mYO/5Qq/wOOiWgHA + NaiDiepgUJ2GI5eq2V8D8nahRANCAARMK9aKUd/H28qaU+0qvS6bSJItzAge1VHn + OhBAAUVci1RpmUA+KdCL5sw9nadAEiONeiGr+28RYHZmlB9qXnjC + -----END PRIVATE KEY----- + "#} + .to_string(), + ), }; Self {