Backport of:

From 670299ceb2f3010fa778eb981a37c41dfe36e2e9 Mon Sep 17 00:00:00 2001
From: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Date: Mon, 23 Feb 2026 11:01:57 +1300
Subject: [PATCH 07/38] CVE-2026-3012: do not fetch certificate over http

In the case where a certificate was found via HTTP, it was trusted
without verification and put in the global CA store.

There is no means to check the certificate other than by comparing it
to certificates we may have gathered via LDAP, but in that case there
is no advantage over just using the LDAP-derived certificates.

Using the LDAP certificates was already the fallback case if HTTP
failed, so we just make it the default.

The HTTP fetch depends on the NDES service, which is a variant of
Simple Certificate Enrolment Protocol (SCEP, RFC8894), but in fact
Samba implements none of that protocol other than the HTTP fetch. SCEP
is for clients that are not true domain members. Domain members can
access to certificates over LDAP. This patch is not reducing SCEP
client support because Samba never had it.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=16003

Reported-by: Arad Inbar, DREAM Security Research Team
Reported-by: Nir Somech, DREAM Security Research Team
Reported-by: Ben Grinberg, DREAM Security Research Team

Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
 python/samba/gp/gp_cert_auto_enroll_ext.py | 52 ++++------------------
 selftest/knownfail.d/gpo-auto-enrol        |  2 +
 2 files changed, 10 insertions(+), 44 deletions(-)
 create mode 100644 selftest/knownfail.d/gpo-auto-enrol

--- /dev/null
+++ b/selftest/knownfail.d/gpo-auto-enrol
@@ -0,0 +1,2 @@
+^samba\.tests\.gpo\.samba\.tests\.gpo\.GPOTests\.test_advanced_gp_cert_auto_enroll_ext\(ad_dc:local\)
+^samba\.tests\.gpo\.samba\.tests\.gpo\.GPOTests\.test_gp_cert_auto_enroll_ext\(ad_dc:local\)
--- a/python/samba/gp/gp_cert_auto_enroll_ext.py
+++ b/python/samba/gp/gp_cert_auto_enroll_ext.py
@@ -16,7 +16,6 @@
 
 import os
 import operator
-import requests
 from samba.gp.gpclass import gp_pol_ext, gp_applier, GPOSTATE
 from samba import Ldb
 from ldb import SCOPE_SUBTREE, SCOPE_BASE
@@ -200,60 +199,24 @@ def get_supported_templates(server):
     return out.strip().split()
 
 
-def getca(ca, url, trust_dir):
-    """Fetch Certificate Chain from the CA."""
+def getca(ca, trust_dir):
+    """Fetch a certificate from LDAP."""
     root_cert = os.path.join(trust_dir, '%s.crt' % ca['name'])
     root_certs = []
-
-    try:
-        r = requests.get(url=url, params={'operation': 'GetCACert',
-                                          'message': 'CAIdentifier'})
-    except requests.exceptions.ConnectionError:
-        log.warn('Failed to establish a new connection')
-        r = None
-    if r is None or r.content == b'' or r.headers['Content-Type'] == 'text/html':
-        log.warn('Failed to fetch the root certificate chain.')
-        log.warn('The Network Device Enrollment Service is either not' +
-                 ' installed or not configured.')
-        if 'cACertificate' in ca:
-            log.warn('Installing the server certificate only.')
-            der_certificate = base64.b64decode(ca['cACertificate'])
-            try:
-                cert = load_der_x509_certificate(der_certificate)
-            except TypeError:
-                cert = load_der_x509_certificate(der_certificate,
-                                                 default_backend())
-            cert_data = cert.public_bytes(Encoding.PEM)
-            with open(root_cert, 'wb') as w:
-                w.write(cert_data)
-            root_certs.append(root_cert)
-        return root_certs
-
-    if r.headers['Content-Type'] == 'application/x-x509-ca-cert':
-        # Older versions of load_der_x509_certificate require a backend param
+    if 'cACertificate' in ca:
+        log.warn('Installing the server certificate only.')
+        der_certificate = base64.b64decode(ca['cACertificate'])
         try:
-            cert = load_der_x509_certificate(r.content)
+            cert = load_der_x509_certificate(der_certificate)
         except TypeError:
-            cert = load_der_x509_certificate(r.content, default_backend())
+            cert = load_der_x509_certificate(der_certificate,
+                                             default_backend())
         cert_data = cert.public_bytes(Encoding.PEM)
         with open(root_cert, 'wb') as w:
             w.write(cert_data)
         root_certs.append(root_cert)
-    elif r.headers['Content-Type'] == 'application/x-x509-ca-ra-cert':
-        certs = load_der_pkcs7_certificates(r.content)
-        for i in range(0, len(certs)):
-            cert = certs[i].public_bytes(Encoding.PEM)
-            filename, extension = root_cert.rsplit('.', 1)
-            dest = '%s.%d.%s' % (filename, i, extension)
-            with open(dest, 'wb') as w:
-                w.write(cert)
-            root_certs.append(dest)
-    else:
-        log.warn('getca: Wrong (or missing) MIME content type')
-
     return root_certs
 
-
 def find_global_trust_dir():
     """Return the global trust dir using known paths from various Linux distros."""
     for trust_dir in global_trust_dirs:
@@ -273,8 +236,7 @@ def changed(new_data, old_data):
 def cert_enroll(ca, ldb, trust_dir, private_dir, auth='Kerberos'):
     """Install the root certificate chain."""
     data = dict({'files': [], 'templates': []}, **ca)
-    url = 'http://%s/CertSrv/mscep/mscep.dll/pkiclient.exe?' % ca['hostname']
-    root_certs = getca(ca, url, trust_dir)
+    root_certs = getca(ca, trust_dir)
     data['files'].extend(root_certs)
     global_trust_dir = find_global_trust_dir()
     for src in root_certs:
