From d635f19dbfe15fe0e5d9c8aa8b8b7a8a2f08405d Mon Sep 17 00:00:00 2001
From: Matt Whitlock <c-lightning@mattwhitlock.name>
Date: Sat, 23 Aug 2025 19:16:30 -0400
Subject: [PATCH] plugins: generate certificates with required extensions

Recent versions of urllib3 fail certificate verification if certificates
lack the Authority Key Identifier or Key Usages extensions:

```
SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Missing Authority Key Identifier (_ssl.c:1032)
SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: CA cert does not include key usage extension (_ssl.c:1032)
```

Luckily, rcgen offers parameters in its CertificateParams structure to
add these extensions. Let's use them.

Changelog-Fixed: Certificates auto-generated by grpc-plugin, rest-plugin, and wss-proxy-plugin now include the required Authority Key Identifier and Key Usages extensions.
---
 plugins/grpc-plugin/src/tls.rs        | 13 +++++++++----
 plugins/rest-plugin/src/certs.rs      |  6 ++++++
 plugins/wss-proxy-plugin/src/certs.rs |  6 ++++++
 3 files changed, 21 insertions(+), 4 deletions(-)

diff --git a/plugins/grpc-plugin/src/tls.rs b/plugins/grpc-plugin/src/tls.rs
index 545c447b4b59e3507d4a07702c1eaeb8ad8473da..18f72e819068c3f56a291c2c86228f12b515fb0b 100644
--- a/plugins/grpc-plugin/src/tls.rs
+++ b/plugins/grpc-plugin/src/tls.rs
@@ -96,14 +96,19 @@ fn generate_or_load_identity(
         // Configure the certificate we want.
         let subject_alt_names = vec!["cln".to_string(), "localhost".to_string()];
         let mut params = rcgen::CertificateParams::new(subject_alt_names)?;
-        params.is_ca = if parent.is_none() {
-            rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained)
+        if parent.is_none() {
+            params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
+            params.key_usages.push(rcgen::KeyUsagePurpose::KeyCertSign);
         } else {
-            rcgen::IsCa::NoCa
-        };
+            params.is_ca = rcgen::IsCa::NoCa;
+            params.key_usages.push(rcgen::KeyUsagePurpose::DigitalSignature);
+            params.key_usages.push(rcgen::KeyUsagePurpose::KeyEncipherment);
+            params.key_usages.push(rcgen::KeyUsagePurpose::KeyAgreement);
+        }
         params
             .distinguished_name
             .push(rcgen::DnType::CommonName, name);
+        params.use_authority_key_identifier_extension = true;
 
         let cert = match parent {
             None => params.self_signed(&keypair),
diff --git a/plugins/rest-plugin/src/certs.rs b/plugins/rest-plugin/src/certs.rs
index d01c1e55a45b6c7a09f81a855f3c75dbef2adb8d..e6ab61345609587cbbf3e16d3039d06e1e1412c5 100644
--- a/plugins/rest-plugin/src/certs.rs
+++ b/plugins/rest-plugin/src/certs.rs
@@ -12,6 +12,8 @@ pub fn generate_certificates(certs_path: &PathBuf, rest_host: &str) -> Result<()
         "localhost".to_string(),
     ])?;
     ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
+    ca_params.key_usages.push(rcgen::KeyUsagePurpose::KeyCertSign);
+    ca_params.use_authority_key_identifier_extension = true;
     let ca_key = KeyPair::generate()?;
     let ca_cert = ca_params.self_signed(&ca_key)?;
 
@@ -30,6 +32,10 @@ pub fn generate_certificates(certs_path: &PathBuf, rest_host: &str) -> Result<()
         "localhost".to_string(),
     ])?;
     server_params.is_ca = rcgen::IsCa::NoCa;
+    server_params.key_usages.push(rcgen::KeyUsagePurpose::DigitalSignature);
+    server_params.key_usages.push(rcgen::KeyUsagePurpose::KeyEncipherment);
+    server_params.key_usages.push(rcgen::KeyUsagePurpose::KeyAgreement);
+    server_params.use_authority_key_identifier_extension = true;
     server_params.distinguished_name = DistinguishedName::new();
     server_params
         .distinguished_name
diff --git a/plugins/wss-proxy-plugin/src/certs.rs b/plugins/wss-proxy-plugin/src/certs.rs
index 2e56b26ddc1883a2975f6138677b75ff6e1f6f0c..b08b7d3e77b744036c93ccdec3dc3147f2f79f04 100644
--- a/plugins/wss-proxy-plugin/src/certs.rs
+++ b/plugins/wss-proxy-plugin/src/certs.rs
@@ -18,6 +18,8 @@ pub fn generate_certificates(certs_path: &PathBuf, wss_host: &[String]) -> Resul
         "localhost".to_string(),
     ])?;
     ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
+    ca_params.key_usages.push(rcgen::KeyUsagePurpose::KeyCertSign);
+    ca_params.use_authority_key_identifier_extension = true;
     let ca_key = KeyPair::generate()?;
     let ca_cert = ca_params.self_signed(&ca_key)?;
 
@@ -36,6 +38,10 @@ pub fn generate_certificates(certs_path: &PathBuf, wss_host: &[String]) -> Resul
         "localhost".to_string(),
     ])?;
     server_params.is_ca = rcgen::IsCa::NoCa;
+    server_params.key_usages.push(rcgen::KeyUsagePurpose::DigitalSignature);
+    server_params.key_usages.push(rcgen::KeyUsagePurpose::KeyEncipherment);
+    server_params.key_usages.push(rcgen::KeyUsagePurpose::KeyAgreement);
+    server_params.use_authority_key_identifier_extension = true;
     server_params.distinguished_name = DistinguishedName::new();
     server_params
         .distinguished_name
