comparison src/server/server.py @ 813:6e27604ec95a

server: added --tls_private_key and --tls_chain options. --tls_certificate .pem file will be used for private_key if --tls_private_key is not specified.
author Goffi <goffi@goffi.org>
date Sun, 20 Dec 2015 20:01:42 +0100
parents fd6965c16e7e
children cf1812a4445e
comparison
equal deleted inserted replaced
812:fd6965c16e7e 813:6e27604ec95a
49 import libervia 49 import libervia
50 50
51 try: 51 try:
52 import OpenSSL 52 import OpenSSL
53 from twisted.internet import ssl 53 from twisted.internet import ssl
54 ssl_available = True 54 except ImportError:
55 except: 55 ssl = None
56 ssl_available = False
57 56
58 from libervia.server.constants import Const as C 57 from libervia.server.constants import Const as C
59 from libervia.server.blog import MicroBlog 58 from libervia.server.blog import MicroBlog
60 59
61 60
1341 else: 1340 else:
1342 self._startService() 1341 self._startService()
1343 1342
1344 self.initialised.addCallback(initOk) 1343 self.initialised.addCallback(initOk)
1345 1344
1345 ## TLS related methods ##
1346
1347 def _TLSOptionsCheck(self):
1348 """Check options coherence if TLS is activated, and update missing values
1349
1350 Must be called only if TLS is activated
1351 """
1352 if not self.options['tls_certificate']:
1353 log.error(u"a TLS certificate is needed to activate HTTPS connection")
1354 self.quit(1)
1355 if not self.options['tls_private_key']:
1356 self.options['tls_private_key'] = self.options['tls_certificate']
1357
1358
1359 if not self.options['tls_private_key']:
1360 self.options['tls_private_key'] = self.options['tls_certificate']
1361
1362 def _loadCertificates(self, f):
1363 """Read a .pem file with a list of certificates
1364
1365 @param f (file): file obj (opened .pem file)
1366 @return (list[OpenSSL.crypto.X509]): list of certificates
1367 @raise OpenSSL.crypto.Error: error while parsing the file
1368 """
1369 # XXX: didn't found any method to load a .pem file with several certificates
1370 # so the certificates split is done here
1371 certificates = []
1372 buf = []
1373 while True:
1374 line = f.readline()
1375 buf.append(line)
1376 if '-----END CERTIFICATE-----' in line:
1377 certificates.append(OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, ''.join(buf)))
1378 buf=[]
1379 elif not line:
1380 log.debug(u"{} certificate(s) found".format(len(certificates)))
1381 return certificates
1382
1383 def _loadPKey(self, f):
1384 """Read a private key from a .pem file
1385
1386 @param f (file): file obj (opened .pem file)
1387 @return (list[OpenSSL.crypto.PKey]): private key object
1388 @raise OpenSSL.crypto.Error: error while parsing the file
1389 """
1390 return OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, f.read())
1391
1392 def _loadCertificate(self, f):
1393 """Read a public certificate from a .pem file
1394
1395 @param f (file): file obj (opened .pem file)
1396 @return (list[OpenSSL.crypto.X509]): public certificate
1397 @raise OpenSSL.crypto.Error: error while parsing the file
1398 """
1399 return OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, f.read())
1400
1401 def _getTLSContextFactory(self):
1402 """Load TLS certificate and build the context factory needed for listenSSL"""
1403 if ssl is None:
1404 raise ImportError(u"Python module pyOpenSSL is not installed!")
1405
1406 cert_options = {}
1407
1408 for name, option, method in [('privateKey', 'tls_private_key', self._loadPKey),
1409 ('certificate', 'tls_certificate', self._loadCertificate),
1410 ('extraCertChain', 'tls_chain', self._loadCertificates)]:
1411 path = self.options[option]
1412 if not path:
1413 assert option=='tls_chain'
1414 continue
1415 log.debug(u"loading {option} from {path}".format(option=option, path=path))
1416 try:
1417 with open(path) as f:
1418 cert_options[name] = method(f)
1419 except IOError as e:
1420 log.error(u"Error while reading file {path} for option {option}: {error}".format(path=path, option=option, error=e))
1421 self.quit(2)
1422 except OpenSSL.crypto.Error:
1423 log.error(u"Error while parsing file {path} for option {option}, are you sure it is a valid .pem file?".format(path=path, option=option))
1424 if option=='tls_private_key' and self.options['tls_certificate'] == path:
1425 log.error(u"You are using the same file for private key and public certificate, make sure that both a in {path} or use --tls_private_key option".format(path=path))
1426 self.quit(2)
1427
1428 return ssl.CertificateOptions(**cert_options)
1429
1430 ## service management ##
1431
1346 def _startService(self, dummy=None): 1432 def _startService(self, dummy=None):
1347 """Actually start the HTTP(S) server(s) after the profile for Libervia is connected. 1433 """Actually start the HTTP(S) server(s) after the profile for Libervia is connected.
1434
1435 @raise ImportError: OpenSSL is not available
1348 @raise IOError: the certificate file doesn't exist 1436 @raise IOError: the certificate file doesn't exist
1349 @raise OpenSSL.crypto.Error: the certificate file is invalid 1437 @raise OpenSSL.crypto.Error: the certificate file is invalid
1350 """ 1438 """
1351 if self.options['connection_type'] in ('https', 'both'): 1439 if self.options['connection_type'] in ('https', 'both'):
1352 if ssl is None: 1440 self._TLSOptionsCheck()
1353 raise ImportError(u"Python module pyOpenSSL is not installed!") 1441 context_factory = self._getTLSContextFactory()
1354 try: 1442 reactor.listenSSL(self.options['port_https'], self.site, context_factory)
1355 with open(os.path.expanduser(self.options['ssl_certificate'])) as keyAndCert:
1356 try:
1357 cert = ssl.PrivateCertificate.loadPEM(keyAndCert.read())
1358 except OpenSSL.crypto.Error as e:
1359 log.error(_(u"The file '%s' must contain both private and public parts of the certificate") % self.options['ssl_certificate'])
1360 raise e
1361 except IOError as e:
1362 log.error(_(u"The file '%s' doesn't exist") % self.options['ssl_certificate'])
1363 raise e
1364 reactor.listenSSL(self.options['port_https'], self.site, cert.options())
1365 if self.options['connection_type'] in ('http', 'both'): 1443 if self.options['connection_type'] in ('http', 'both'):
1366 if self.options['connection_type'] == 'both' and self.options['redirect_to_https']: 1444 if self.options['connection_type'] == 'both' and self.options['redirect_to_https']:
1367 reactor.listenTCP(self.options['port'], server.Site(RedirectToHTTPS(self.options['port'], self.options['port_https_ext']))) 1445 reactor.listenTCP(self.options['port'], server.Site(RedirectToHTTPS(self.options['port'], self.options['port_https_ext'])))
1368 else: 1446 else:
1369 reactor.listenTCP(self.options['port'], self.site) 1447 reactor.listenTCP(self.options['port'], self.site)