Correct socket use in cert_expiry platform (#25011)

* Make sure we use same family for ssl socket and connection

getaddrinfo result could be different from what connection
was made with. It also blocks potential use of
happy eye balls algorithm

This also fixes lingering sockets until python garbage
collection.

* Add availability value if unable to get expiry

* Fix lint issue
This commit is contained in:
Joakim Plate 2019-07-08 11:33:23 +02:00 committed by Pascal Vizeli
parent 31d7b702a6
commit c2f1c4b981

View File

@ -55,6 +55,7 @@ class SSLCertificate(Entity):
self.server_port = server_port
self._name = sensor_name
self._state = None
self._available = False
@property
def name(self):
@ -76,34 +77,39 @@ class SSLCertificate(Entity):
"""Icon to use in the frontend, if any."""
return 'mdi:certificate'
@property
def available(self):
"""Icon to use in the frontend, if any."""
return self._available
def update(self):
"""Fetch the certificate information."""
ctx = ssl.create_default_context()
try:
ctx = ssl.create_default_context()
host_info = socket.getaddrinfo(self.server_name, self.server_port)
family = host_info[0][0]
sock = ctx.wrap_socket(
socket.socket(family=family), server_hostname=self.server_name)
sock.settimeout(TIMEOUT)
sock.connect((self.server_name, self.server_port))
address = (self.server_name, self.server_port)
with socket.create_connection(
address, timeout=TIMEOUT) as sock:
with ctx.wrap_socket(
sock, server_hostname=address[0]) as ssock:
cert = ssock.getpeercert()
except socket.gaierror:
_LOGGER.error("Cannot resolve hostname: %s", self.server_name)
self._available = False
return
except socket.timeout:
_LOGGER.error(
"Connection timeout with server: %s", self.server_name)
self._available = False
return
except OSError:
_LOGGER.error("Cannot connect to %s", self.server_name)
return
try:
cert = sock.getpeercert()
except OSError:
_LOGGER.error("Cannot fetch certificate from %s", self.server_name)
_LOGGER.error("Cannot fetch certificate from %s",
self.server_name, exc_info=1)
self._available = False
return
ts_seconds = ssl.cert_time_to_seconds(cert['notAfter'])
timestamp = datetime.fromtimestamp(ts_seconds)
expiry = timestamp - datetime.today()
self._available = True
self._state = expiry.days