Commit 3266582f authored by Julien Muchembled's avatar Julien Muchembled

Use pyOpenSSL instead of spawning 'openssl' subprocesses

parent b55cc1c9
...@@ -32,6 +32,7 @@ setup( ...@@ -32,6 +32,7 @@ setup(
keywords="slapos networkcache shadir shacache", keywords="slapos networkcache shadir shacache",
install_requires=[ install_requires=[
'setuptools', # for namespace 'setuptools', # for namespace
'pyOpenSSL',
] + additional_install_requires, ] + additional_install_requires,
classifiers=[ classifiers=[
'Development Status :: 4 - Beta', 'Development Status :: 4 - Beta',
......
...@@ -17,11 +17,10 @@ import httplib ...@@ -17,11 +17,10 @@ import httplib
import json import json
import os import os
import socket import socket
import subprocess
import tempfile
import traceback import traceback
import urllib2 import urllib2
import urlparse import urlparse
from OpenSSL import crypto
# XXX: code between select/select_generic must be factored # XXX: code between select/select_generic must be factored
...@@ -74,8 +73,8 @@ class NetworkcacheClient(object): ...@@ -74,8 +73,8 @@ class NetworkcacheClient(object):
- SHADIR - SHADIR
- SHACACHE - SHACACHE
''' '''
signature_private_key = None
openssl = 'openssl'
def parseUrl(self, url): def parseUrl(self, url):
return_dict = {} return_dict = {}
parsed_url = urlparse.urlparse(url) parsed_url = urlparse.urlparse(url)
...@@ -142,17 +141,20 @@ class NetworkcacheClient(object): ...@@ -142,17 +141,20 @@ class NetworkcacheClient(object):
for k, v in self.parseUrl(shadir).iteritems(): for k, v in self.parseUrl(shadir).iteritems():
setattr(self, 'shadir_%s' % k, v) setattr(self, 'shadir_%s' % k, v)
self.signature_private_key_file = signature_private_key_file if signature_private_key_file:
with open(signature_private_key_file) as f:
self.signature_private_key = crypto.load_privatekey(crypto.FILETYPE_PEM,
f.read())
if type(signature_certificate_list) is str: if type(signature_certificate_list) is str:
# If signature_certificate_list is a string, parse it to a list of # If signature_certificate_list is a string, parse it to a list of
# certificates # certificates
cert_marker = "-----BEGIN CERTIFICATE-----" cert_marker = "-----BEGIN CERTIFICATE-----"
parsed_signature_certificate_list = [cert_marker + '\n' + q.strip() \ signature_certificate_list = [cert_marker + '\n' + q.strip() \
for q in signature_certificate_list.split(cert_marker) \ for q in signature_certificate_list.split(cert_marker) \
if q.strip()] if q.strip()]
self.signature_certificate_list = parsed_signature_certificate_list self.signature_certificate_list = [
else: crypto.load_certificate(crypto.FILETYPE_PEM, certificate)
self.signature_certificate_list = signature_certificate_list for certificate in signature_certificate_list or ()]
self.shacache_key_file = shacache_key_file self.shacache_key_file = shacache_key_file
self.shacache_cert_file = shacache_cert_file self.shacache_cert_file = shacache_cert_file
...@@ -205,11 +207,8 @@ class NetworkcacheClient(object): ...@@ -205,11 +207,8 @@ class NetworkcacheClient(object):
def index(self, key, **kw): def index(self, key, **kw):
data = json.dumps(kw) data = json.dumps(kw)
try: data = [data, self._getSignatureString(data)]
data = [data, self._getSignatureString(data)]
except Exception:
raise UploadError('Impossible to sign content, error:\n%s'
% traceback.format_exc())
if self.shadir_scheme == 'https': if self.shadir_scheme == 'https':
shadir_connection = httplib.HTTPSConnection(self.shadir_host, shadir_connection = httplib.HTTPSConnection(self.shadir_host,
self.shadir_port, key_file=self.shadir_key_file, self.shadir_port, key_file=self.shadir_key_file,
...@@ -253,19 +252,14 @@ class NetworkcacheClient(object): ...@@ -253,19 +252,14 @@ class NetworkcacheClient(object):
except Exception: except Exception:
raise DirectoryNotFound('It was impossible to parse json response:\n%s'% raise DirectoryNotFound('It was impossible to parse json response:\n%s'%
traceback.format_exc()) traceback.format_exc())
filtered_data_list = [] if self.signature_certificate_list:
if self.signature_certificate_list is not None: data_list = [data for data in data_list
for data in data_list: if self._verifySignatureInCertificateList(*data)]
if len(data[1]):
if self._verifySignatureInCertificateList(data[0], data[1]):
filtered_data_list.append(data)
else:
filtered_data_list = data_list
if len(filtered_data_list) == 0: if not data_list:
raise DirectoryNotFound('Could not find a trustable entry.') raise DirectoryNotFound('Could not find a trustable entry.')
information_json, signature = filtered_data_list[0] information_json, signature = data_list[0]
try: try:
information_dict = json.loads(information_json) information_dict = json.loads(information_json)
except Exception: except Exception:
...@@ -291,52 +285,29 @@ class NetworkcacheClient(object): ...@@ -291,52 +285,29 @@ class NetworkcacheClient(object):
except Exception: except Exception:
raise DirectoryNotFound('It was impossible to parse json response:\n%s' % raise DirectoryNotFound('It was impossible to parse json response:\n%s' %
traceback.format_exc()) traceback.format_exc())
filtered_data_list = [] return [data for data in data_list
for data in data_list: if self._verifySignatureInCertificateList(*data)]
if len(data[1]):
if self._verifySignatureInCertificateList(data[0], data[1]):
filtered_data_list.append(data)
return filtered_data_list
def _openssl(self, input, *args):
p = subprocess.Popen((self.openssl,) + args,
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
output = p.communicate(input)[0]
if p.returncode:
raise subprocess.CalledProcessError(p.returncode, self.openssl, output)
return output
def _getSignatureString(self, content): def _getSignatureString(self, content):
""" """
Return the signature based on certification file. Return the signature based on certification file.
""" """
if self.signature_private_key_file is None: k = self.signature_private_key
return '' return '' if k is None else crypto.sign(k, content, 'sha1').encode('base64')
return self._openssl(content, "dgst", "-sha1", "-sign",
self.signature_private_key_file).encode('base64')
def _verifySignatureInCertificateList(self, content, signature_string): def _verifySignatureInCertificateList(self, content, signature_string):
""" """
Returns true if it can find any valid certificate or false if it does not Returns true if it can find any valid certificate or false if it does not
find any. find any.
""" """
if self.signature_certificate_list: if signature_string:
with tempfile.NamedTemporaryFile() as signature_file: signature = signature_string.decode('base64')
signature_file.write(signature_string.decode('base64')) for certificate in self.signature_certificate_list:
signature_file.flush() try:
for certificate in self.signature_certificate_list: crypto.verify(certificate, signature, content, 'sha1')
try: return True
pubkey = self._openssl(certificate, "x509", "-pubkey", "-noout") except crypto.Error:
with tempfile.NamedTemporaryFile() as pubkey_file: pass
pubkey_file.write(pubkey)
pubkey_file.flush()
if self._openssl(content, "dgst", "-sha1", "-verify",
pubkey_file.name, "-signature", signature_file.name
).startswith('Verified OK'):
return True
except Exception:
# in case of failure, emit *anything*, but swallow all what possible
traceback.print_exc()
return False return False
......
...@@ -250,19 +250,19 @@ MYZmKV7A3nFehN9A+REz+WU3I7fE6vQRh9jKeuxnQLRv0TdP9CEdPcYcs/EQpIDb ...@@ -250,19 +250,19 @@ MYZmKV7A3nFehN9A+REz+WU3I7fE6vQRh9jKeuxnQLRv0TdP9CEdPcYcs/EQpIDb
-----END CERTIFICATE----- -----END CERTIFICATE-----
""" """
alternate_certificate = """ -----BEGIN CERTIFICATE----- alternate_certificate = """-----BEGIN CERTIFICATE-----
MIIB4DCCAUkCADANBgkqhkiG9w0BAQsFADA5MQswCQYDVQQGEwJGUjEZMBcGA1UE MIIB4DCCAUkCADANBgkqhkiG9w0BAQsFADA5MQswCQYDVQQGEwJGUjEZMBcGA1UE
CBMQRGVmYXVsdCBQcm92aW5jZTEPMA0GA1UEChMGTmV4ZWRpMB4XDTExMDkxNTA5 CBMQRGVmYXVsdCBQcm92aW5jZTEPMA0GA1UEChMGTmV4ZWRpMB4XDTExMDkxNTA5
MDAwMloXDTEyMDkxNTA5MDAwMlowOTELMAkGA1UEBhMCRlIxGTAXBgNVBAgTEERl MDAwMloXDTEyMDkxNTA5MDAwMlowOTELMAkGA1UEBhMCRlIxGTAXBgNVBAgTEERl
ZmF1bHQgUHJvdmluY2UxDzANBgNVBAoTBk5leGVkaTCBnzANBgkqhkiG9w0BAQEF ZmF1bHQgUHJvdmluY2UxDzANBgNVBAoTBk5leGVkaTCBnzANBgkqhkiG9w0BAQEF
AAOBjQAwgYkCgYEApYZv6OstoqNzxG1KI6iE5U4Ts2Xx9lgLeUGAMyfJLyMmRLhw AAOBjQAwgYkCgYEApYZv6OstoqNzxG1KI6iE5U4Ts2Xx9lgLeUGAMyfJLyMmRLhw
boKOyJ9Xke4dncoBAyNPokUR6iWOcnPHtMvNOsBFZ2f7VA28em3+E1JRYdeNUEtX boKOyJ9Xke4dncoBAyNPokUR6iWOcnPHtMvNOsBFZ2f7VA28em3+E1JRYdeNUEtX
Z0s3HjcouaNAnPfjFTXHYj4um1wOw2cURSPuU5dpzKBbV+/QCb5DLheynisCAwEA Z0s3HjcouaNAnPfjFTXHYj4um1wOw2cURSPuU5dpzKBbV+/QCb5DLheynisCAwEA
ATANBgkqhkiG9w0BAQsFAAOBgQBCZLbTVdrw3RZlVVMFezSHrhBYKAukTwZrNmJX ATANBgkqhkiG9w0BAQsFAAOBgQBCZLbTVdrw3RZlVVMFezSHrhBYKAukTwZrNmJX
mHqi2tN8tNo6FX+wmxUUAf3e8R2Ymbdbn2bfbPpcKQ2fG7PuKGvhwMG3BlF9paEC mHqi2tN8tNo6FX+wmxUUAf3e8R2Ymbdbn2bfbPpcKQ2fG7PuKGvhwMG3BlF9paEC
q7jdfWO18Zp/BG7tagz0jmmC4y/8akzHsVlruo2+2du2freE8dK746uoMlXlP93g q7jdfWO18Zp/BG7tagz0jmmC4y/8akzHsVlruo2+2du2freE8dK746uoMlXlP93g
QUUGLQ== QUUGLQ==
-----END CERTIFICATE----- -----END CERTIFICATE-----
""" """
ca_cert = """-----BEGIN CERTIFICATE----- ca_cert = """-----BEGIN CERTIFICATE-----
...@@ -452,68 +452,6 @@ class OnlineTest(OnlineMixin, unittest.TestCase): ...@@ -452,68 +452,6 @@ class OnlineTest(OnlineMixin, unittest.TestCase):
result = signed_nc.select(key) result = signed_nc.select(key)
self.assertEqual(result.read(), self.test_string) self.assertEqual(result.read(), self.test_string)
def test_upload_signed_content_openssl_not_available(self):
key = 'somekey' + str(random.random())
urlmd5 = str(random.random())
file_name = 'my file'
key_file = tempfile.NamedTemporaryFile()
key_file.write(self.key)
key_file.flush()
signed_nc = slapos.libnetworkcache.NetworkcacheClient(
self.shacache, self.shadir, key_file.name, [self.certificate])
signed_nc.openssl = '/doesnotexists'
try:
signed_nc.upload(self.test_data, key, urlmd5=urlmd5, file_name=file_name)
self.fail("UploadError not raised")
except slapos.libnetworkcache.UploadError, e:
self.assertTrue(str(e).startswith("Impossible to sign content, error"))
def test_upload_signed_content_openssl_non_functional(self):
key = 'somekey' + str(random.random())
urlmd5 = str(random.random())
file_name = 'my file'
key_file = tempfile.NamedTemporaryFile()
key_file.write(self.key)
key_file.flush()
signed_nc = slapos.libnetworkcache.NetworkcacheClient(
self.shacache, self.shadir, key_file.name, [self.certificate])
signed_nc.openssl = sys.executable
try:
signed_nc.upload(self.test_data, key, urlmd5=urlmd5, file_name=file_name)
self.fail("UploadError not raised")
except slapos.libnetworkcache.UploadError, e:
self.assertTrue(str(e).startswith("Impossible to sign content, error"))
def test_select_signed_content_openssl_not_available(self):
key = 'somekey' + str(random.random())
urlmd5 = str(random.random())
file_name = 'my file'
key_file = tempfile.NamedTemporaryFile()
key_file.write(self.key)
key_file.flush()
signed_nc = slapos.libnetworkcache.NetworkcacheClient(
self.shacache, self.shadir, key_file.name, [self.certificate])
self.assertEqual(self.test_shasum, signed_nc.upload(self.test_data,
key, urlmd5=urlmd5, file_name=file_name))
signed_nc.openssl = '/doesnotexists'
self.assertDirectoryNotFound('Could not find a trustable entry.',
signed_nc.select, key)
def test_select_signed_content_openssl_non_functional(self):
key = 'somekey' + str(random.random())
urlmd5 = str(random.random())
file_name = 'my file'
key_file = tempfile.NamedTemporaryFile()
key_file.write(self.key)
key_file.flush()
signed_nc = slapos.libnetworkcache.NetworkcacheClient(
self.shacache, self.shadir, key_file.name, [self.certificate])
self.assertEqual(self.test_shasum, signed_nc.upload(self.test_data,
key, urlmd5=urlmd5, file_name=file_name))
signed_nc.openssl = sys.executable
self.assertDirectoryNotFound('Could not find a trustable entry.',
signed_nc.select, key)
def test_select_no_entries(self): def test_select_no_entries(self):
key = 'somekey' + str(random.random()) key = 'somekey' + str(random.random())
urlmd5 = str(random.random()) urlmd5 = str(random.random())
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment