Uygulamalı Eğitimler

Python'da TLS üzerinden MQTT (CA, istemci sertifikaları)

paho-mqtt'yi TLS ile bir MQTT broker'ına bağlayın: tls_set() seçenekleri, özel CA dosyaları, istemci sertifikalarıyla mutual TLS ve yaygın SSL hatalarının çözümü.

Güncellendi · 9 dk okuma

Python'da TLS üzerinden MQTT (CA, istemci sertifikaları)

paho-mqtt'yi TLS üzerinden bağlamak için connect()'ten önce client.tls_set() çağırın ve broker'ın TLS portunu, genellikle 8883'ü kullanın. Argümansız çağrıldığında paho 2.x broker sertifikasını sisteminizin güvenilir CA'larına göre doğrular ve hostname'i kontrol eder; herkese açık sertifikalı bir broker için tam olarak istediğiniz budur. Özel bir CA için ca_certs= verirsiniz, mutual TLS için de certfile= ve keyfile= eklersiniz.

Bu rehber her tls_set() seçeneğini, eksiksiz bir mutual TLS örneğini ve paho'nun fırlattığı SSL hatalarını birebir metinleriyle, her birinin nasıl doğrulanıp düzeltileceğiyle birlikte ele alıyor.

En basit TLS bağlantısı

tls_minimal.py
import os
import time

import paho.mqtt.client as mqtt


def on_connect(client, userdata, flags, reason_code, properties):
    print("CONNACK:", reason_code)
    client.subscribe("testmqtt/tls-demo/#")


def on_message(client, userdata, msg):
    print(msg.topic, msg.payload.decode())


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="tls-demo-1")
client.username_pw_set(os.environ["MQTT_USER"], os.environ["MQTT_PASS"])
client.tls_set()  # sistem CA deposu, TLS 1.2+, hostname doğrulaması açık
client.on_connect = on_connect
client.on_message = on_message

client.connect("broker.connectmq.com", 8883, keepalive=60)
client.loop_start()
client.publish("testmqtt/tls-demo/hello", "encrypted hello", qos=1)
time.sleep(2)
client.loop_stop()
client.disconnect()

İki kural sorunların çoğunu önler:

  • IP adresiyle değil, sertifikadaki hostname ile bağlanın. paho, connect()'e verdiğiniz host'u SNI olarak gönderir ve sertifikayla karşılaştırır.
  • Port ile protokolü eşleştirin. TLS 8883'e (secure WebSocket için 8084'e), şifresiz TCP 1883'e gider. Bunları karıştırmak kafa karıştırıcı hatalara yol açar; aşağıda açıklanıyor. Tam liste için MQTT portları yazısına bakın.

tls_set() seçenekleri

Argümanpaho 2.x'te varsayılanNe zaman kullanılır
ca_certsNone → işletim sistemi güven deposuBroker özel veya self-signed bir CA kullanıyorsa: CA PEM dosyasının yolu
certfileNoneBroker istemci sertifikası istiyorsa (mutual TLS)
keyfileNoneAynı dosyada değilse certfile'ın özel anahtarı
keyfile_passwordNoneAnahtar şifreliyse; aksi halde paho terminalde sorabilir
cert_reqsssl.CERT_REQUIREDNeredeyse hiç değiştirmeyin; ssl.CERT_NONE doğrulamayı kapatır
tls_versionssl.PROTOCOL_TLS_CLIENT (en yüksek sürümü müzakere eder)Nadiren; kendi context'inizde minimum sürüm belirlemeyi tercih edin
ciphersOpenSSL varsayılanlarıBelirli bir cipher politikasına uymanız gerekiyorsa
alpn_protocolsNoneUç nokta ALPN'e göre yönlendiriyorsa, örneğin 443 portunda MQTT

İki ilgili metot tabloyu tamamlar:

  • tls_insecure_set(True) zincir doğrulamasını korur ama hostname kontrolünü atlar. tls_set()'ten sonra çağrılmalıdır; aksi halde paho ValueError: Must configure SSL context before using tls_insecure_set. fırlatır.
  • tls_set_context(ctx) kendi ssl.SSLContext nesnenizi kabul eder. tls_set()'in sunmadığı ayarlara ihtiyaç duyduğunuzda kullanın; TLS istemci başına yalnızca bir kez yapılandırılabilir: ikinci çağrı ValueError: SSL/TLS has already been configured. fırlatır.
custom_context.py
import ssl

ctx = ssl.create_default_context(cafile="/etc/mqtt/ca.crt")  # doğrulama + hostname kontrolü
ctx.minimum_version = ssl.TLSVersion.TLSv1_2
client.tls_set_context(ctx)

Özel CA veya self-signed broker

Yerel ağınızdaki bir Mosquitto broker'ı genellikle kendi CA'nızın imzaladığı bir sertifikaya sahiptir. paho'ya CA sertifikasını verin (broker sertifikasını değil ve asla özel anahtarı değil):

private_ca.py
client.tls_set(ca_certs="/etc/mqtt/ca.crt")
client.connect("mqtt.home.internal", 8883)

Bağlandığınız hostname, broker sertifikasının Subject Alternative Name alanında bulunmalıdır. IP ile bağlanıyorsanız IP, SAN'da IP: girdisi olarak yer almalıdır; bir DNS: girdisi bunu kapsamaz. Emin değilseniz kontrolleri kapatmak yerine sunucu sertifikasını doğru SAN ile yeniden oluşturun. Adım adım anlatım self-signed sertifika hatalarını çözme yazısında.

İstemci sertifikalarıyla mutual TLS

Mutual TLS'te broker da istemciyi doğrular. Her cihaz, broker'ın güvendiği bir CA tarafından imzalanmış kendi sertifikasını alır; birçok broker sertifikanın CN'ini MQTT kullanıcı adına eşleyebilir.

mtls_client.py
import ssl

import paho.mqtt.client as mqtt

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="device-0042")
client.tls_set(
    ca_certs="/etc/mqtt/ca.crt",          # broker sertifikasını imzalayan CA
    certfile="/etc/mqtt/device-0042.crt",  # bu istemcinin sertifikası (PEM)
    keyfile="/etc/mqtt/device-0042.key",   # özel anahtarı (PEM)
    keyfile_password=None,                 # veya bir secret store'dan okuyun
    cert_reqs=ssl.CERT_REQUIRED,
)
client.connect("mqtt.example.internal", 8883)
client.loop_forever()

İnsanların takıldığı noktalar:

  • certfile, istemci sertifikasını ve ardından ara (intermediate) CA'ları içerebilir; broker yalnızca root'a güveniyorsa bu gereklidir.
  • Anahtarlar PEM olmalıdır. Bir PKCS#12 paketini openssl pkcs12 -in device.p12 -out device.crt -clcerts -nokeys ve openssl pkcs12 -in device.p12 -out device.key -nocerts -nodes ile dönüştürün.
  • Anahtar dosyasını koruyun (chmod 600) ve asla commit etmeyin.

Broker'ınızın istemci sertifikalarını destekleyip desteklemediği ve bunları kimliklere nasıl eşlediği broker'a bağlıdır. Kavramlar ve broker tarafı kurulum MQTT istemci sertifikaları ve mTLS yazısında.

paho'da yaygın SSL hataları ve çözümleri

connect() ile TLS hataları doğrudan exception olarak fırlatılır ve okunması kolaydır. connect_async() ve loop_start() ile ağ thread'i bunun yerine yeniden denemeye devam eder; hatayı görmek için client.enable_logger() çağırın. (_ssl.c:NNNN) eki Python sürümüne göre değişir; ondan önceki metni arayın.

certificate verify failed: unable to get local issuer certificate

Hata
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1006)
  • Neden: Python, broker sertifikasını veren CA'yı bulamıyor. Ya broker özel bir CA kullanıyor ya da Python'unuzda CA paketi yok (macOS'taki python.org derlemelerinde tipiktir).
  • Doğrulama: python3 -c "import ssl; print(ssl.get_default_verify_paths())" Python'un nereye baktığını gösterir; openssl s_client (aşağıya bakın) sertifikayı kimin verdiğini gösterir.
  • Çözüm: özel CA için ca_certs="ca.crt" verin. Paket eksikse macOS'ta /Applications/Python 3.x/Install Certificates.command'ı çalıştırın ya da pip install certifi yapıp tls_set(ca_certs=certifi.where()) kullanın.

self-signed certificate (in certificate chain)

Hata
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain (_ssl.c:1006)
  • Neden: zincir, istemcinizin güvenmediği bir root ile bitiyor. Eski OpenSSL derlemeleri bunu self signed certificate olarak yazar.
  • Çözüm: root CA'yı ca_certs= ile verin. Broker sertifikasının kendisi self-signed ise (CA yoksa) o dosyayı ca_certs olarak verin, daha iyisi sertifikayı küçük bir CA'dan üretin.

Hostname mismatch

Hata
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Hostname mismatch, certificate is not valid for 'localhost'. (_ssl.c:1006)
  • Neden: connect()'teki ad veya IP, sertifikanın SAN listesinde yok.
  • Doğrulama: openssl s_client -connect host:8883 -servername host </dev/null 2>/dev/null | openssl x509 -noout -ext subjectAltName.
  • Çözüm: SAN'da bulunan bir adla bağlanın veya sertifikayı yeniden üretin. tls_insecure_set(True) sorunu yalnızca gizler ve production'a girmemelidir. Ayrıntılar MQTT hostname mismatch yazısında.

certificate has expired

Hata
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:1006)
  • Neden: broker sertifikası (veya bir ara sertifika) notAfter tarihini geçmiş ya da cihazın saati yanlış. RTC'si olmayıp 1970'te açılan cihazlar tersini yaşar: certificate is not yet valid.
  • Çözüm: sertifikayı yenileyin; cihazlarda bağlanmadan önce saati senkronize edin (NTP).

Yanlış port: EOF veya wrong version number

Hatalar
ssl.SSLEOFError: [SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol (_ssl.c:1006)
ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1006)
  • Neden: tls_set() çağırdınız ama şifresiz bir porta bağlandınız. Bir MQTT listener'ı genellikle bağlantıyı kapatır (EOF veya ConnectionResetError); HTTP veya başka TLS olmayan byte'larla yanıt veren bir port WRONG_VERSION_NUMBER verir.
  • Çözüm: TLS portunu (8883) kullanın. Ters hata, yani 8883'e tls_set() olmadan bağlanmak, işe yarar bir şey fırlatmaz: broker bağlantıyı düşürür ve hiçbir zaman CONNACK almazsınız.

İstemci sertifikası reddedildi (mutual TLS)

Hatalar
ssl.SSLError: [SSL: TLSV13_ALERT_CERTIFICATE_REQUIRED] tlsv13 alert certificate required (_ssl.c:2580)
ssl.SSLError: [SSL: TLSV1_ALERT_UNKNOWN_CA] tlsv1 alert unknown ca (_ssl.c:1006)
ssl.SSLError: [SSL] PEM lib (_ssl.c:3900)
  • certificate required: broker bir istemci sertifikası istiyor ve siz göndermediniz; certfile/keyfile ekleyin. TLS 1.3'te bu uyarı handshake'ten hemen sonra gelebilir, bu yüzden exception yerine anında kopma olarak görünebilir.
  • unknown ca: broker, istemci sertifikanızı imzalayan CA'ya güvenmiyor ya da ara sertifikayı göndermediniz. Broker'ın CA dosyasını düzeltin veya ara sertifikayı certfile'ın sonuna ekleyin.
  • PEM lib veya [X509: KEY_VALUES_MISMATCH] key values mismatch: sertifika veya anahtar okunamıyor, farklı bir şifreyle şifrelenmiş, PEM değil ya da anahtar sertifikaya ait değil. RSA anahtarları için openssl x509 -noout -modulus -in device.crt | openssl md5 ile openssl rsa -noout -modulus -in device.key | openssl md5 çıktısını karşılaştırın.

paho'nun diğer hataları için Python paho-mqtt hataları yazısına bakın.

Çözümü nasıl test edersiniz

Önce Python'dan bağımsız olarak broker tarafını kontrol edin:

Broker sertifikasını inceleme
openssl s_client -connect broker.connectmq.com:8883 \
  -servername broker.connectmq.com -showcerts </dev/null
# şunu arayın: Verify return code: 0 (ok)

Ardından çalıştığını bildiğiniz bir istemcinin aynı dosyalarla çalıştığını doğrulayın:

Aynı kontrol mosquitto_sub ile
mosquitto_sub -h broker.connectmq.com -p 8883 \
  --capath /etc/ssl/certs \
  -u "$MQTT_USER" -P "$MQTT_PASS" \
  -t 'testmqtt/tls-demo/#' -v -C 1

Son olarak Python script'inizi loglama açık şekilde çalıştırın; kalan sorun varsa ekrana yazılır:

debug_tls.py
import logging

logging.basicConfig(level=logging.DEBUG)
client.enable_logger()

openssl başarılı olup Python başarısız oluyorsa fark neredeyse her zaman Python'un kullandığı CA paketidir. İkisi de başarısızsa sorun broker'da veya ağdadır. Tüm yol için adım adım bir kontrol listesi MQTT TLS bağlantısını test etme yazısında; broker'ı secure WebSocket üzerinden online MQTT istemcisi ile de çapraz kontrol edebilirsiniz.

paho'da TLS, doğru yapılmış tek bir çağrıya indirgenir: doğru CA, doğru port ve sertifikayla eşleşen bir hostname ile tls_set(). Doğrulamayı açık tutun, kontrolleri kapatmak yerine sertifikaları düzeltin; ek kod yazmadan şifreleme ve broker kimlik doğrulaması elde edersiniz.

Sıkça sorulan sorular

tls_set() argümansız çağrıldığında ne yapar?

paho-mqtt 2.x'te Python'un desteklediği en yüksek TLS sürümüyle bir istemci TLS context'i oluşturur, işletim sisteminin güvenilir CA sertifikalarını yükler, geçerli bir sunucu sertifikası ister ve hostname'in eşleştiğini kontrol eder. Herkesçe güvenilen sertifikalara sahip broker'lar için doğru ayar budur.

tls_insecure_set(True) ile cert_reqs=ssl.CERT_NONE arasındaki fark nedir?

tls_insecure_set(True) sertifika zincirini doğrulamaya devam eder ama hostname kontrolünü atlar. cert_reqs=ssl.CERT_NONE doğrulamayı tamamen kapatır ve her sertifikayı kabul eder. İkisi de man-in-the-middle saldırılarına kapı açar ve yalnızca kısa yerel testlerde kullanılmalıdır.

paho neden certificate verify failed: unable to get local issuer certificate diyor?

Python, broker sertifikasını imzalayan CA'yı bulamadı. Ya broker özel bir CA kullanıyor (dosyasını ca_certs= ile verin) ya da Python'unuzda CA paketi yok; macOS'taki python.org kurulumlarında bu yaygındır. Install Certificates.command'ı çalıştırarak veya ca_certs=certifi.where() vererek çözün.