async def start(self) -> None: """Start the aiohttp server.""" context: ssl.SSLContext | None if self.ssl_certificate: try: if self.ssl_profile == SSL_INTERMEDIATE: context = ssl_util.server_context_intermediate() else: context = ssl_util.server_context_modern() await self.hass.async_add_executor_job( context.load_cert_chain, self.ssl_certificate, self.ssl_key ) except OSError as error: _LOGGER.error( "Could not read SSL certificate from %s: %s", self.ssl_certificate, error, ) return if self.ssl_peer_certificate: context.verify_mode = ssl.CERT_REQUIRED await self.hass.async_add_executor_job( context.load_verify_locations, self.ssl_peer_certificate ) else: context = None # Aiohttp freezes apps after start so that no changes can be made. # However in Home Assistant components can be discovered after boot. # This will now raise a RunTimeError. # To work around this we now prevent the router from getting frozen # pylint: disable=protected-access self.app._router.freeze = lambda: None # type: ignore[assignment] self.runner = web.AppRunner(self.app) await self.runner.setup() self.site = HomeAssistantTCPSite( self.runner, self.server_host, self.server_port, ssl_context=context ) try: await self.site.start() except OSError as error: _LOGGER.error( "Failed to create HTTP server at port %d: %s", self.server_port, error ) _LOGGER.info("Now listening on port %d", self.server_port)
def _create_emergency_ssl_context(self) -> ssl.SSLContext: """Create an emergency ssl certificate so we can still startup.""" context = ssl_util.server_context_modern() host: str try: host = cast(str, URL(get_url(self.hass, prefer_external=True)).host) except NoURLAvailableError: host = "homeassistant.local" key = rsa.generate_private_key( public_exponent=65537, key_size=2048, ) subject = issuer = x509.Name( [ x509.NameAttribute( NameOID.ORGANIZATION_NAME, "Home Assistant Emergency Certificate" ), x509.NameAttribute(NameOID.COMMON_NAME, host), ] ) cert = ( x509.CertificateBuilder() .subject_name(subject) .issuer_name(issuer) .public_key(key.public_key()) .serial_number(x509.random_serial_number()) .not_valid_before(datetime.datetime.utcnow()) .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=30)) .add_extension( x509.SubjectAlternativeName([x509.DNSName(host)]), critical=False, ) .sign(key, hashes.SHA256()) ) with NamedTemporaryFile() as cert_pem, NamedTemporaryFile() as key_pem: cert_pem.write(cert.public_bytes(serialization.Encoding.PEM)) key_pem.write( key.private_bytes( serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ) ) cert_pem.flush() key_pem.flush() context.load_cert_chain(cert_pem.name, key_pem.name) return context
async def start(self): """Start the aiohttp server.""" # We misunderstood the startup signal. You're not allowed to change # anything during startup. Temp workaround. # pylint: disable=protected-access self.app._on_startup.freeze() await self.app.startup() if self.ssl_certificate: try: if self.ssl_profile == SSL_INTERMEDIATE: context = ssl_util.server_context_intermediate() else: context = ssl_util.server_context_modern() await self.hass.async_add_executor_job( context.load_cert_chain, self.ssl_certificate, self.ssl_key) except OSError as error: _LOGGER.error("Could not read SSL certificate from %s: %s", self.ssl_certificate, error) return if self.ssl_peer_certificate: context.verify_mode = ssl.CERT_REQUIRED await self.hass.async_add_executor_job( context.load_verify_locations, self.ssl_peer_certificate) else: context = None # Aiohttp freezes apps after start so that no changes can be made. # However in Home Assistant components can be discovered after boot. # This will now raise a RunTimeError. # To work around this we now prevent the router from getting frozen self.app._router.freeze = lambda: None self.runner = web.AppRunner(self.app) await self.runner.setup() self.site = web.TCPSite(self.runner, self.server_host, self.server_port, ssl_context=context) try: await self.site.start() except OSError as error: _LOGGER.error("Failed to create HTTP server at port %d: %s", self.server_port, error)
def _create_ssl_context(self) -> ssl.SSLContext | None: context: ssl.SSLContext | None = None assert self.ssl_certificate is not None try: if self.ssl_profile == SSL_INTERMEDIATE: context = ssl_util.server_context_intermediate() else: context = ssl_util.server_context_modern() context.load_cert_chain(self.ssl_certificate, self.ssl_key) except OSError as error: if not self.hass.config.safe_mode: raise HomeAssistantError( f"Could not use SSL certificate from {self.ssl_certificate}: {error}" ) from error _LOGGER.error( "Could not read SSL certificate from %s: %s", self.ssl_certificate, error, ) try: context = self._create_emergency_ssl_context() except OSError as error2: _LOGGER.error( "Could not create an emergency self signed ssl certificate: %s", error2, ) context = None else: _LOGGER.critical( "Home Assistant is running in safe mode with an emergency self signed ssl certificate because the configured SSL certificate was not usable" ) return context if self.ssl_peer_certificate: if context is None: raise HomeAssistantError( "Failed to create ssl context, no fallback available because a peer certificate is required." ) context.verify_mode = ssl.CERT_REQUIRED context.load_verify_locations(self.ssl_peer_certificate) return context