plugins: generate certificates with required extensions
What changed, and why it matters
This commit fixes the automatically generated TLS certificates used by three Core Lightning plugins (grpc-plugin, rest-plugin, and wss-proxy-plugin). Recent versions of the urllib3 library started rejecting these certificates because they were missing two standard fields: the Authority Key Identifier and Key Usage extensions. Without these fields, Python-based clients connecting to the plugins could fail TLS verification and refuse to connect. The patch adds the missing certificate extensions so verification succeeds again.
Upgrade to a Core Lightning release containing this commit if you use the grpc-plugin, rest-plugin, or wss-proxy-plugin and have clients relying on urllib3 or other strict TLS verifiers. After upgrading, regenerate or delete existing auto-generated certificates so the plugins create new compliant certificates on startup. No immediate incident response is required; this is a compatibility/fix commit rather than an active vulnerability.
Security signals we found
TLS certificate verification failure in client connections
Missing X.509 Key Usage extension on generated CA and server certificates
Missing Authority Key Identifier extension on generated certificates
Client-side SSL handshake errors with urllib3
Patch adds standard required certificate extensions
Evidence from the diff
The commit modifies certificate generation in three Rust plugins to include X.509 extensions now required by urllib3’s certificate verification. For CA certificates it adds KeyUsagePurpose::KeyCertSign and enables use_authority_key_identifier_extension. For leaf/server certificates it adds DigitalSignature, KeyEncipherment, and KeyAgreement, and also enables the Authority Key Identifier extension. This resolves SSLError failures from urllib3 such as ‘Missing Authority Key Identifier’ and ‘CA cert does not include key usage extension’.
Changed components
plugins/grpc-plugin/src/tls.rsplugins/rest-plugin/src/certs.rsplugins/wss-proxy-plugin/src/certs.rsInspect captured patch +21 / −4
diff --git a/plugins/grpc-plugin/src/tls.rs b/plugins/grpc-plugin/src/tls.rs
index 545c447b..18f72e81 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 d01c1e55..e6ab6134 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 2e56b26d..b08b7d3e 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
Why this scored 47/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.