From bb755ef7d2aa6186f07bde47b15d8d28a1618477 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Sat, 18 Jul 2026 01:59:28 +0200 Subject: [PATCH] fix(gamestream): generate the RSA host identity via the `rsa` crate, not rcgen (ring can't RSA-keygen) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The workspace went ring-only in 0.13.0 (aws-lc-sys breaks Windows CI), and `ring` can sign with an existing RSA key but cannot *generate* one — rcgen's ring backend returns `KeyGenerationUnavailable` for `generate_for(&PKCS_RSA_SHA256)`. So `ServerIdentity::{load_or_create, ephemeral}` panics with "rcgen RSA keygen / There is no support for generating keys for the given algorithm" on any host without an existing `cert.pem`. This is the shared trust root for both the GameStream TLS cert and the QUIC identity clients pin, so a *fresh* 0.13.0 install can't start the host at all (existing dev boxes survive only because they already have a cert on disk); in CI every test that builds a `ServerIdentity` (all of `mgmt::tests`, `gamestream::nvhttp`, and the `native::tests` synthetic host via `serve()`) failed — 30 failures from this one cause. Moonlight requires an RSA-2048 identity, so generate the key with the pure-Rust `rsa` crate (already a dependency for the pairing signer) and hand its PKCS#8 PEM to rcgen, whose ring backend *can* load and self-sign with an externally supplied RSA key (`from_pkcs8_pem_and_sign_algo` → `RsaKeyPair::from_pkcs8`). Return that same PEM so it stays byte-identical to what `from_pems` re-parses. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/punktfunk-host/src/gamestream/cert.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/crates/punktfunk-host/src/gamestream/cert.rs b/crates/punktfunk-host/src/gamestream/cert.rs index 073f310e..9276fd14 100644 --- a/crates/punktfunk-host/src/gamestream/cert.rs +++ b/crates/punktfunk-host/src/gamestream/cert.rs @@ -5,7 +5,7 @@ use anyhow::{anyhow, Context, Result}; use pf_paths::config_dir; use rsa::pkcs1v15::SigningKey; -use rsa::pkcs8::DecodePrivateKey; +use rsa::pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding}; use rsa::RsaPrivateKey; use sha2::Sha256; use std::fs; @@ -70,7 +70,20 @@ impl ServerIdentity { } fn generate() -> Result<(String, String)> { - let key = rcgen::KeyPair::generate_for(&rcgen::PKCS_RSA_SHA256).context("rcgen RSA keygen")?; + // The workspace is ring-only (aws-lc-sys breaks Windows CI — see the rustls/rcgen pins), and + // `ring` can *sign* with an existing RSA key but cannot *generate* one: rcgen's ring backend + // returns `KeyGenerationUnavailable` for `generate_for(&PKCS_RSA_SHA256)`. Moonlight requires an + // RSA-2048 identity, so generate the key with the pure-Rust `rsa` crate (already a dep for the + // pairing signer) and hand the PKCS#8 PEM to rcgen, whose ring backend *can* load + self-sign + // with it. Returning that same PEM keeps it byte-identical to what `from_pems` re-parses. + let mut rng = rand::thread_rng(); + let priv_key = RsaPrivateKey::new(&mut rng, 2048).context("generate RSA-2048 host key")?; + let key_pem = priv_key + .to_pkcs8_pem(LineEnding::LF) + .context("encode host key as PKCS#8 PEM")? + .to_string(); + let key = rcgen::KeyPair::from_pkcs8_pem_and_sign_algo(&key_pem, &rcgen::PKCS_RSA_SHA256) + .context("load RSA host key into rcgen")?; let mut params = rcgen::CertificateParams::new(Vec::::new()).context("cert params")?; params .distinguished_name @@ -78,7 +91,7 @@ fn generate() -> Result<(String, String)> { params.not_before = rcgen::date_time_ymd(2020, 1, 1); params.not_after = rcgen::date_time_ymd(2040, 1, 1); let cert = params.self_signed(&key).context("self-sign cert")?; - Ok((cert.pem(), key.serialize_pem())) + Ok((cert.pem(), key_pem)) } /// Extract the X.509 `signatureValue` bytes from a cert PEM.