Un’app C++ su Windows Server 2025 fallisce l’LDAPS con 0x51 e Wireshark sembra indicare un ClientHello “TLS 1.0”. In realtà il server negozia TLS 1.2/1.3: ecco perché nasce il falso allarme e come risolvere davvero tra certificati, porte, MTU e Schannel.
Scenario e sintomi
Su Windows Server 2025 Standard (24H2, build 26100) un’applicazione C++ basata sulle API WinLDAP (ldapsslinit
+ ldapconnect
) non riesce a stabilire una connessione LDAPS verso un server LDAP. La chiamata a ldapconnect
ritorna 0x51 (LDAPSERVER_DOWN). Con Wireshark, l’autore osserva un ClientHello etichettato come TLS 1.0, nonostante nel Registro i protocolli TLS 1.0/1.1 risultino disattivati e siano attivi TLS 1.2/1.3. La stessa utility, eseguita su Windows 11 contro lo stesso server, completa l’handshake in TLS 1.2 e la sessione LDAPS funziona.
Interpretazioni iniziali e tentativi non risolutivi
Proposta | Idea di fondo | Esito |
---|---|---|
Criteri di gruppo → “Disattiva supporto crittografia” (Internet Explorer → Pannello di controllo Internet → Pagina Avanzate) impostato su Abilitata con solo TLS 1.0 | Forzare/impedire protocolli TLS a livello di WinInet/IE | Nessun cambiamento: Wireshark continua a mostrare ClientHello “TLS 1.0”. |
Centro connessioni di rete → Impostazioni avanzate | Verificare se TLS 1.0 è spuntato nell’elenco protocolli | TLS 1.0 già deselezionato; nessun effetto. |
Inviare feedback con strumenti di diagnostica Microsoft | Possibile bug nella pila TLS di Windows Server 2025 | Suggerito come passo successivo, ma non necessario. |
Perché Wireshark “sembra” indicare TLS 1.0 quando in realtà è TLS 1.2/1.3
Il punto chiave è comprendere la differenza tra la versione “legacy” nel record layer e la versione effettivamente negoziata dal protocollo.
- Nelle implementazioni moderne, il campo Record Layer Version del primo pacchetto può riportare
0x0301
(TLS 1.0) o0x0303
(TLS 1.2) per compatibilità con middlebox e vecchi dispositivi di rete. Questo valore non determina la versione che sarà negoziata. - La versione reale è pubblicizzata nell’estensione Supported Versions del ClientHello. Lì vedrai tipicamente TLS 1.3 (0x0304) e TLS 1.2 (0x0303). Se il server supporta TLS 1.3 o TLS 1.2, la negoziazione avviene a quel livello, indipendentemente da come Wireshark etichetta la riga “Record Layer: TLS 1.0”.
- Interpretazioni ambigue o release non aggiornate di Wireshark possono enfatizzare la voce “TLS 1.0” nel pannello, generando un false positive.
Morale: Windows Server 2025 non “forza” TLS 1.0 su LDAPS. Se sul sistema TLS 1.0/1.1 sono disattivati (impostazione predefinita in Schannel sulle build recenti), il ClientHello includerà TLS 1.2/1.3 e la negoziazione userà il miglior protocollo comune.
Risoluzione effettiva (TL;DR)
- Il presunto uso di TLS 1.0 era un falso allarme dovuto alla lettura del campo errato o a un’etichettatura discutibile dello sniffer.
- Lo 0x51 (LDAPSERVER_DOWN) va ricondotto ad altre cause: porta o hostname errato, catena certificati incompleta o non fidata, firewall/MTU, mismatch SNI, policy di sicurezza, ecc.
Checklist di diagnostica rapida
- Conferma versione TLS reale:
- Abilita i log di Schannel/TLS e verifica gli Event ID 36874/36888.
- Esegui un test esterno:
openssl sclient -connect <host>:636 -servername <FQDN> -tls13 -showcerts
e, se necessario, ripeti con-tls1_2
. Se l’handshake completa, il server accetta TLS 1.3/1.2.
- Porta/servizio corretti:
- LDAPS:
636/tcp
, canale già cifrato. - LDAP con STARTTLS:
389/tcp
+ estensione STARTTLS. - Test veloce:
Test-NetConnection <host> -Port 636
(PowerShell).
- LDAPS:
- Certificati e trust:
- Il certificato server deve avere EKU Server Authentication e SAN con l’FQDN usato dal client (SNI/DNS).
- Importa la catena completa fino a una radice fidata nel Trusted Root Certification Authorities del client.
- Valida con
certutil -verify -urlfetch server_cert.cer
.
- Firewall/MTU:
- Un ClientHello ampio può frammentarsi e venire scartato da middlebox: verifica MTU (
ping -f -l 1472
) e considera il path IPv6. - Se necessario, riduci temporaneamente la lista di cipher suite consentite per restringere il ClientHello.
- Un ClientHello ampio può frammentarsi e venire scartato da middlebox: verifica MTU (
- Policy e stack corretti:
- Le API WinLDAP si appoggiano a Schannel, non a WinInet: le spunte “TLS” nel pannello Internet non governano LDAPS.
- Controlla i percorsi Registro di Schannel per protocolli abilitati/disabilitati.
Approfondimento: come Windows Server 2025 negozia TLS per LDAPS
In Windows, le API WinLDAP (wldap32.dll
) demandano la sicurezza del trasporto al provider Schannel. Da build recenti di Windows Server 2025:
- TLS 1.0/1.1 sono disattivati per impostazione predefinita a livello di Schannel, salvo override espliciti via Criteri di gruppo o Registro (
HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols
). - TLS 1.2/1.3 sono attivi e usati come predefiniti.
Per verificare/forzare i protocolli consentiti, i percorsi tipici sono:
HKEYLOCALMACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Client
DisabledByDefault = 1 (DWORD) // disabilitato
Enabled = 0 (DWORD)
HKEYLOCALMACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Client
DisabledByDefault = 1
Enabled = 0
HKEYLOCALMACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client
DisabledByDefault = 0
Enabled = 1
HKEYLOCALMACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Client
DisabledByDefault = 0
Enabled = 1
Nota: le impostazioni di Internet Explorer/WinInet (Pannello di controllo Internet → Avanzate) non governano le connessioni LDAPS; tali spunte impattano i componenti che usano WinInet o WinHTTP, non wldap32
.
Perché il record layer può essere “TLS 1.0”
Il ClientHello moderno incapsula la versione reale nell’estensione Supported Versions. Il record layer può mostrare un valore “legacy” (talvolta 0x0301) per compatibilità. In Wireshark, espandi “Handshake Protocol: Client Hello” e osserva Supported Versions per leggere la realtà.
Cause comuni di LDAPSERVER_DOWN (0x51)
Categoria | Causa tipica | Verifica/Correzione |
---|---|---|
Porte | Uso di 389 senza STARTTLS o tentativo su 636 ma servizio non in ascolto | Test-NetConnection host -Port 636 . Se usi 389, avvia ldapstarttls_s prima del bind. |
DNS/SNI | Hostname errato o privo di SAN nel certificato; mancato SNI | Usa l’FQDN presente nel SAN del certificato; in OpenSSL aggiungi -servername . Con WinLDAP, usa l’FQDN corretto. |
Trust | CA non fidata o catena incompleta | Importa intermedi e radice; verifica con certutil -verify -urlfetch . |
Cipher/Policy | Server e client non condividono cipher suite; FIPS mode attivo | Controlla Get-TlsCipherSuite . Disabilita policy FIPS se non necessaria o allinea le suite. |
MTU/Middlebox | Frammentazione del ClientHello e drop da firewall/IPS | Verifica MTU e riduci temporaneamente le suite (GPO “TLS Cipher Suite Order”) per restringere il ClientHello. |
Cert revocation | CRL/OCSP non raggiungibili → fallisce il controllo revoca | Abilita accesso verso endpoint CRL/OCSP o disabilita temporaneamente il check a scopo diagnostico. |
Binding | Uso di 389 con bind semplice senza STARTTLS in ambienti che impongono “LDAP signing” | Usa LDAPS (636) o negozia STARTTLS prima del bind. |
Strumenti utili e comandi
Eventi Schannel/TLS
Attiva i log dettagliati di Schannel per diagnosticare gli handshake:
reg add "HKLM\System\CurrentControlSet\Control\SecurityProviders\Schannel" ^
/v EventLogging /t REG_DWORD /d 7 /f
Controlla il registro eventi: “Applicazioni e servizi → Microsoft → Windows → Schannel/Operational”. In caso di errori, cerca Event ID 36874 (failure di protocollo/cipher) e 36888 (fatal alert).
OpenSSL dal server o da un host di test
# Test TLS 1.3
openssl sclient -connect ldap.example.com:636 -servername ldap.example.com -tls13 -showcerts
Test TLS 1.2
openssl sclient -connect ldap.example.com:636 -servername ldap.example.com -tls12 -showcerts
PowerShell: cipher suite e porta
# Elenco delle cipher suite abilitate lato client
Get-TlsCipherSuite | Sort-Object Name | Format-Table Name, Protocols, Exchange
Test apertura porta 636
Test-NetConnection ldap.example.com -Port 636
CertUtil: validare la catena
CertUtil -verify -urlfetch C:\temp\server_cert.cer
Netsh trace per handshaking TCP/TLS
netsh trace start scenario=NetConnection capture=yes report=yes
rem Esegui il test LDAPS
netsh trace stop
Wireshark: leggere correttamente il ClientHello
- Filtra:
tcp.port == 636
. - Apri il pacchetto con “Handshake Protocol: Client Hello”.
- Controlla la sezione Supported Versions e non solo la riga del “Record Layer”.
- Assicurati di usare una versione aggiornata (consigliata ≥ 4.2) per etichette corrette su TLS 1.3.
Guida completa passo‑passo alla correzione
- Verifica connessione di base: pinga l’FQDN, esegui
Test-NetConnection -Port 636
. Se fallisce, priorità a DNS/routing/firewall. - Conferma TLS:
- OpenSSL: handshake ok con TLS 1.2/1.3? Se sì, la rete e il server LDAPS sono sani.
- Eventi Schannel: esistono alert di protocollo o cipher incompatibili?
- Valida il certificato server:
- Il SAN deve includere l’FQDN che usi (es.
ldap.example.com
). - EKU: Server Authentication. Catena completa installata e fidata sul client.
- Il SAN deve includere l’FQDN che usi (es.
- Riduci le variabili:
- Prova con l’utility Ldp.exe (inbox Windows). Se Ldp.exe si connette in LDAPS, il problema è nell’app o nei parametri di bind.
- Testa sia LDAPS (636) sia LDAP + STARTTLS (389) per isolare differenze di percorso/MTU.
- MTU e middlebox:
- Se il ClientHello è grande (molte cipher suite/estensioni), verifica che non avvenga frammentazione su link con MTU ristretta.
- Temporaneamente, ordina/riduci le cipher suite via GPO “TLS Cipher Suite Order” per snellire il ClientHello.
- Policy di sicurezza:
- Se è abilitata la policy “Usa algoritmi compatibili FIPS”, alcune suite moderne possono essere escluse.
- Assicurati che non vi siano override in GPO che riattivano TLS 1.0/1.1 o disabilitano TLS 1.2/1.3.
- Riprova dall’app C++ con log dettagliati e controlla l’esito di
ldapconnect
,ldapget_option
per l’ultimo errore eGetLastError()
per indizi aggiuntivi.
Esempi di codice C++ (WinLDAP) corretti
LDAPS nativo sulla porta 636
// Compila con: wldap32.lib
#include <windows.h>
#include <winldap.h>
#pragma comment(lib, "wldap32.lib")
int wmain() {
ULONG version = LDAP_VERSION3;
// secure = 1 per LDAPS (canale TLS dal connect)
LDAP* ld = ldap_sslinitW(L"ldap.example.com", 636, 1);
if (!ld) {
wprintf(L"ldap_sslinitW fallita, err=%lu\n", GetLastError());
return 1;
}
```
ULONG rc = ldap_set_optionW(ld, LDAP_OPT_PROTOCOL_VERSION, &version);
if (rc != LDAP_SUCCESS) {
wprintf(L"ldap_set_optionW(PROTOCOL_VERSION)=%lu\n", rc);
ldap_unbind(ld);
return 1;
}
// Facoltativo: timeout di connessione
timeval tv{10, 0}; // 10 secondi
rc = ldap_connect(ld, &tv);
if (rc != LDAP_SUCCESS) {
wprintf(L"ldap_connect=%lu (0x%lx)\n", rc, rc);
ldap_unbind(ld);
return 1;
}
// A questo punto il canale TLS è stabilito via Schannel.
// Esegui un bind (esempio: simple bind con UPN)
SEC_WINNT_AUTH_IDENTITY_W auth{};
// ...riempi 'auth' se usi SASL/GSSAPI o credenziali esplicite...
// Esempio di simple bind (sconsigliato senza TLS)
rc = ldap_simple_bind_sW(ld, L"user@example.com", L"Password#2025!");
if (rc != LDAP_SUCCESS) {
wprintf(L"ldap_simple_bind_sW=%lu (0x%lx)\n", rc, rc);
} else {
wprintf(L"Bind riuscito.\n");
}
ldap_unbind(ld);
return 0;
```
}
LDAP + STARTTLS sulla porta 389
// Stabilisce una connessione LDAP "in chiaro",
// poi eleva a TLS con STARTTLS prima del bind.
#include <windows.h>
#include <winldap.h>
#pragma comment(lib, "wldap32.lib")
int wmain() {
ULONG version = LDAP_VERSION3;
LDAP* ld = ldap_initW(L"ldap.example.com", 389);
if (!ld) {
wprintf(L"ldap_initW fallita, err=%lu\n", GetLastError());
return 1;
}
```
ULONG rc = ldap_set_optionW(ld, LDAP_OPT_PROTOCOL_VERSION, &version);
if (rc != LDAP_SUCCESS) {
wprintf(L"ldap_set_optionW=%lu\n", rc);
ldap_unbind(ld);
return 1;
}
// Eleva a TLS prima di ogni bind o query sensibile
rc = ldap_start_tls_sW(ld, nullptr, nullptr, nullptr, nullptr);
if (rc != LDAP_SUCCESS) {
wprintf(L"ldap_start_tls_sW=%lu (0x%lx)\n", rc, rc);
ldap_unbind(ld);
return 1;
}
timeval tv{10, 0};
rc = ldap_connect(ld, &tv);
if (rc != LDAP_SUCCESS) {
wprintf(L"ldap_connect=%lu (0x%lx)\n", rc, rc);
ldap_unbind(ld);
return 1;
}
// Ora il canale è protetto da TLS; puoi eseguire il bind
rc = ldap_simple_bind_sW(ld, L"user@example.com", L"Password#2025!");
if (rc != LDAP_SUCCESS) {
wprintf(L"ldap_simple_bind_sW=%lu (0x%lx)\n", rc, rc);
} else {
wprintf(L"Bind riuscito.\n");
}
ldap_unbind(ld);
return 0;
```
}
Note pratiche per il codice
- Usa Unicode (
ldap_*W
) per evitare problemi di encoding. - Imposta sempre
LDAP_VERSION3
: STARTTLS richiede v3, e in generale è best practice. - Non esiste un’opzione WinLDAP per “forzare TLS 1.3” per singola connessione: la selezione del protocollo e delle suite è demandata a Schannel (policy di sistema).
- Per diagnosticare, interroga
ldapgetoption
conLDAPOPTERRORNUMBER
eLDAPOPTERRORSTRING
dopo un fallimento.
Confermare “cosa” è stato negoziato davvero
Con Schannel event logging
- Event ID 36874: traccia di handshake fallito (ad esempio protokoll mismatch o cipher incompatibile). Le descrizioni indicano spesso protocollo e suite attese.
- Event ID 36888: fatal alert con un codice (es. Handshake Failure), utile per incrociare con il lato server.
Con OpenSSL
L’output di openssl s_client
riporta in chiaro protocollo e cipher suite effettiva, ad esempio:
New, TLSv1.3, Cipher is TLSAES256GCMSHA384
Server public key is 2048 bit
SSL-Session:
Protocol : TLSv1.3
Cipher : TLSAES256GCMSHA384
Ottimizzare il ClientHello (MTU e middlebox ostili)
In alcuni ambienti, la frammentazione dei primi pacchetti TLS viene filtrata. Suggerimenti:
- Misura l’MTU effettiva end‑to‑end con
ping -f -l
e allineala sugli hop critici. - Riduci temporaneamente le cipher suite abilitate via GPO (TLS Cipher Suite Order) per snellire le estensioni del ClientHello.
- Preferisci LDAPS su 636 se il path per 389 subisce ispezioni/riscritture più aggressive.
Domande frequenti
Perché su Windows 11 funziona e su Server 2025 no?
Molto spesso il motivo è non il protocollo TLS: sul client Windows 11 la CA potrebbe già essere fidata, la rete meno filtrata o la catena del certificato più accessibile (CRL/OCSP). Allinea trust store, percorsi e policy.
Posso forzare TLS 1.3 per quell’unica app C++?
Con WinLDAP no: il minimo/massimo protocollo è definito a livello di Schannel (sistema). Per test, puoi disattivare temporaneamente TLS 1.2 lato client, ma non è una pratica consigliata in produzione.
Le spunte TLS nel Pannello Internet possono sbloccare LDAPS?
No. Quelle opzioni si applicano ai componenti che usano WinInet/WinHTTP (browser, alcune librerie), non a wldap32
.
Serve “LDAP signing” se uso LDAPS?
Con LDAPS (TLS) il canale è già cifrato e autenticato; molte policy che richiedono signing sono soddisfatte. Con LDAP semplice su 389, usa sempre STARTTLS prima del bind.
Best practice per Windows Server 2025 e LDAPS
- TLS 1.2/1.3 di default: mantieni TLS 1.0/1.1 disattivati; evita override non necessari.
- Certificati “a prova di futuro”: SAN con FQDN corretti, chiavi ECDSA o RSA ≥ 2048, curve/suite moderne, CRL/OCSP raggiungibili.
- DNS e SNI allineati: usa sempre l’FQDN che appare nel certificato server.
- Monitoraggio: abilita e conserva i log Schannel/TLS in fase di migrazione.
- Toolchain aggiornata: usa Wireshark recente per un parsing accurato di TLS 1.3; aggiorna OpenSSL nei bastioni di test.
Conclusioni
Nel caso esaminato, l’“allarme” su TLS 1.0 era un falso positivo. Windows Server 2025, a parità di policy, negozia TLS 1.2/1.3 su LDAPS come previsto. Quando ldapconnect
ritorna 0x51, concentrare la diagnosi su porte, DNS/SNI, catena di certificati, policy/cipher suite e MTU/middlebox porta quasi sempre alla soluzione. Per una verifica oggettiva dei protocolli, affidati a Schannel event logging e a openssl s_client, non alla singola etichetta “TLS 1.0” sul record layer di Wireshark. In breve: non è Windows Server 2025 a forzare TLS 1.0; è l’interpretazione a essere ingannevole.
Appendice: riferimenti operativi concentrati
- Verifica protocolli reali: Event ID 36874/36888;
openssl sclient -tls13/-tls1_2
. - Stack TLS: WinLDAP usa Schannel; le impostazioni di IE/WinInet non si applicano.
- Default Server 2025: TLS 1.0/1.1 disattivati, TLS 1.2/1.3 abilitati salvo override (
HKLM...\SCHANNEL\Protocols
). - Diagnosi 0x51: controlla porta, certificati, trust, firewall/MTU, cipher suite, policy FIPS.
- Strumenti:
CertUtil -verify -urlfetch
,Get-TlsCipherSuite
,Test-NetConnection
, Wireshark 4.2+.
In sintesi: l’allarme su TLS 1.0 nasce da una lettura parziale dei pacchetti. Windows Server 2025 aderisce alla policy che privilegia TLS 1.2/1.3 di default. Raddrizza il tiro della diagnostica: certificati, rete, configurazione di Schannel e corretto uso delle API WinLDAP fanno la differenza.