Commit 6e0205fc authored by Łukasz Nowak's avatar Łukasz Nowak

Remove M2Crypto dependency.

Squashed commit of the following:

commit 5c74964a312fb743f88a115ffb4d5af44dd3d2d1
Author: Łukasz Nowak <luke@nexedi.com>
Date:   Fri Sep 2 10:22:48 2011 +0200

    No more M2Crypto is required.

commit 71ddc285850bd1c5761b95cff8616c6023f6eadf
Author: Łukasz Nowak <luke@nexedi.com>
Date:   Fri Sep 2 10:22:03 2011 +0200

    Drop M2Crypto dependency.

    Simplify, generate only common name in key.

commit 9e058070bc66fc91a590d90adf379a899b00d124
Author: Łukasz Nowak <luke@nexedi.com>
Date:   Thu Sep 1 18:05:14 2011 +0200

    Cover cases when openssl is not compatible.

    It seems that python executable can be seen as openssl begin not compatible.

commit 69901f159392db65597b97482439bea4d002e9c4
Author: Łukasz Nowak <luke@nexedi.com>
Date:   Thu Sep 1 18:00:27 2011 +0200

    Raise correctly in case of openssl unavailabilty during upload.

    More: raise DirectoryNotFound in more cases.

commit c17c46053385b586e7ccdb4ebf3ecb9ab46d901b
Author: Łukasz Nowak <luke@nexedi.com>
Date:   Thu Sep 1 18:00:08 2011 +0200

    Cover case when no openssl is available during upload.

commit d88d9e2b892000fa64c28b74182777fd03c93556
Author: Łukasz Nowak <luke@nexedi.com>
Date:   Thu Sep 1 17:55:23 2011 +0200

    Covert case when empty shadir entry is in shadir.

commit 6671546db794a5ea8e7025de115a613940e4af14
Author: Łukasz Nowak <luke@nexedi.com>
Date:   Thu Sep 1 17:45:08 2011 +0200

    Add no openssl during content selection test.

    Add another test skeleton.

commit ad6b3b5abbbb2d8267938661946acf1bd4d02b43
Author: Łukasz Nowak <luke@nexedi.com>
Date:   Thu Sep 1 17:32:09 2011 +0200

    Close another temporary file.

commit 34d15b585e52810908323cf846af5a64171f62b2
Author: Łukasz Nowak <luke@nexedi.com>
Date:   Thu Sep 1 17:23:23 2011 +0200

    Expose openssl and avoid dying in case if not exists.

commit 0eb456fc6ccd2d36644c47abc1ef79b7b51187c6
Author: Łukasz Nowak <luke@nexedi.com>
Date:   Thu Sep 1 17:15:54 2011 +0200

    Pep8isation.

commit 7179acf759a1d18bff367025a965d020443f93e2
Author: Łukasz Nowak <luke@nexedi.com>
Date:   Thu Sep 1 17:12:25 2011 +0200

    Drop M2Crypto dependency.

    M2Crypto is unmantained library. It seems safer to use openssl directly to
    sign and verify content.
parent a6acb627
...@@ -32,7 +32,6 @@ setup( ...@@ -32,7 +32,6 @@ setup(
keywords="slapos networkcache shadir shacache", keywords="slapos networkcache shadir shacache",
install_requires=[ install_requires=[
'setuptools', # for namespace 'setuptools', # for namespace
'M2Crypto', # required for certificate checking
] + additional_install_requires, ] + additional_install_requires,
classifiers=[ classifiers=[
'Development Status :: 4 - Beta', 'Development Status :: 4 - Beta',
......
...@@ -18,11 +18,12 @@ import hashlib ...@@ -18,11 +18,12 @@ import hashlib
import httplib import httplib
import json import json
import os import os
import socket
import subprocess
import tempfile import tempfile
import traceback
import urllib2 import urllib2
import urlparse import urlparse
import M2Crypto
import socket
class NetworkcacheClient(object): class NetworkcacheClient(object):
...@@ -33,6 +34,7 @@ class NetworkcacheClient(object): ...@@ -33,6 +34,7 @@ class NetworkcacheClient(object):
- SHACACHE - SHACACHE
''' '''
openssl = 'openssl'
def parseUrl(self, url): def parseUrl(self, url):
return_dict = {} return_dict = {}
parsed_url = urlparse.urlparse(url) parsed_url = urlparse.urlparse(url)
...@@ -161,7 +163,11 @@ class NetworkcacheClient(object): ...@@ -161,7 +163,11 @@ class NetworkcacheClient(object):
kw['architecture'] = architecture kw['architecture'] = architecture
sha_entry = json.dumps(kw) sha_entry = json.dumps(kw)
signature = self._getSignatureString(sha_entry) try:
signature = self._getSignatureString(sha_entry)
except Exception:
raise UploadError('Impossible to sign content, error:\n%s' %
traceback.format_exc())
data = [sha_entry, signature] data = [sha_entry, signature]
if self.shadir_scheme == 'https': if self.shadir_scheme == 'https':
...@@ -190,7 +196,6 @@ class NetworkcacheClient(object): ...@@ -190,7 +196,6 @@ class NetworkcacheClient(object):
It uses http GET request method. It uses http GET request method.
''' '''
sha_cache_url = os.path.join(self.shacache_url, sha512sum) sha_cache_url = os.path.join(self.shacache_url, sha512sum)
file_descriptor = tempfile.NamedTemporaryFile()
request = urllib2.Request(url=sha_cache_url, data=None, request = urllib2.Request(url=sha_cache_url, data=None,
headers=self.shadir_header_dict) headers=self.shadir_header_dict)
return urllib2.urlopen(request) return urllib2.urlopen(request)
...@@ -200,7 +205,6 @@ class NetworkcacheClient(object): ...@@ -200,7 +205,6 @@ class NetworkcacheClient(object):
Raise DirectoryNotFound if multiple files are found. Raise DirectoryNotFound if multiple files are found.
''' '''
url = os.path.join(self.shadir_url, key) url = os.path.join(self.shadir_url, key)
file_descriptor = tempfile.NamedTemporaryFile()
request = urllib2.Request(url=url, data=None, request = urllib2.Request(url=url, data=None,
headers=self.shadir_header_dict) headers=self.shadir_header_dict)
data = urllib2.urlopen(request).read() data = urllib2.urlopen(request).read()
...@@ -211,11 +215,12 @@ class NetworkcacheClient(object): ...@@ -211,11 +215,12 @@ class NetworkcacheClient(object):
for data in data_list: for data in data_list:
if self._verifySignatureInCertificateList(data[0], data[1]): if self._verifySignatureInCertificateList(data[0], data[1]):
filtered_data_list.append(data) filtered_data_list.append(data)
if not filtered_data_list:
raise DirectoryNotFound('Could not find a trustable entry.')
else: else:
filtered_data_list = data_list filtered_data_list = data_list
if len(filtered_data_list) == 0:
raise DirectoryNotFound('Could not find a trustable entry.')
if len(filtered_data_list) > 1: if len(filtered_data_list) > 1:
raise DirectoryNotFound('Too many entries for a given key %r. ' \ raise DirectoryNotFound('Too many entries for a given key %r. ' \
'Entries: %s.' % (key, str(data_list))) 'Entries: %s.' % (key, str(data_list)))
...@@ -232,12 +237,16 @@ class NetworkcacheClient(object): ...@@ -232,12 +237,16 @@ class NetworkcacheClient(object):
if self.signature_private_key_file is None: if self.signature_private_key_file is None:
return '' return ''
SignEVP = M2Crypto.EVP.load_key(self.signature_private_key_file) content_file = tempfile.NamedTemporaryFile()
SignEVP.sign_init() content_file.write(content)
SignEVP.sign_update(content) content_file.flush()
StringSignature = SignEVP.sign_final() content_file.seek(0)
signature = StringSignature.encode('base64') try:
return signature signature = subprocess.check_output([self.openssl, "dgst", "-sha1",
"-sign", self.signature_private_key_file, content_file.name])
return signature.encode('base64')
finally:
content_file.close()
def _verifySignatureInCertificateList(self, content, signature_string): def _verifySignatureInCertificateList(self, content, signature_string):
""" """
...@@ -246,25 +255,55 @@ class NetworkcacheClient(object): ...@@ -246,25 +255,55 @@ class NetworkcacheClient(object):
""" """
if self.signature_certificate_list is not None: if self.signature_certificate_list is not None:
for certificate in self.signature_certificate_list: for certificate in self.signature_certificate_list:
if self._verifySignatureCertificate(content, signature_string, certificate): if self._verifySignatureCertificate(content, signature_string,
certificate):
return True return True
return False return False
def _verifySignatureCertificate(self, content, signature_string, certificate): def _verifySignatureCertificate(self, content, signature_string,
certificate):
""" verify if the signature is valid for a given certificate. """ """ verify if the signature is valid for a given certificate. """
certificate_file = tempfile.NamedTemporaryFile() certificate_file = tempfile.NamedTemporaryFile()
certificate_file.write(certificate) certificate_file.write(certificate)
certificate_file.flush() certificate_file.flush()
certificate_file.seek(0) certificate_file.seek(0)
signature_file = tempfile.NamedTemporaryFile()
signature_file.write(signature_string.decode('base64'))
signature_file.flush()
signature_file.seek(0)
content_file = tempfile.NamedTemporaryFile()
content_file.write(content)
content_file.flush()
content_file.seek(0)
pubkey_file = tempfile.NamedTemporaryFile()
try: try:
PubKey = M2Crypto.X509.load_cert(certificate_file.name) last_output = ''
VerifyEVP = M2Crypto.EVP.PKey() try:
VerifyEVP.assign_rsa(PubKey.get_pubkey().get_rsa()) last_output = subprocess.check_output([self.openssl, "x509", "-pubkey",
VerifyEVP.verify_init() "-noout", "-in", certificate_file.name])
VerifyEVP.verify_update(str(content)) pubkey_file.write(last_output)
return VerifyEVP.verify_final(signature_string.decode('base64')) pubkey_file.flush()
pubkey_file.seek(0)
try:
last_output = subprocess.check_output([self.openssl, "dgst", "-sha1",
"-verify", pubkey_file.name, "-signature", signature_file.name,
content_file.name])
except subprocess.CalledProcessError, e:
# in case if verification failed
last_output = e.output
if last_output.startswith('Verified OK'):
return True
except Exception:
# in case of failure, emit *anything*, but swallow all what possible
print last_output
print traceback.format_exc()
return False
finally: finally:
certificate_file.close() certificate_file.close()
signature_file.close()
content_file.close()
pubkey_file.close()
class DirectoryNotFound(Exception): class DirectoryNotFound(Exception):
pass pass
......
...@@ -15,6 +15,7 @@ import time ...@@ -15,6 +15,7 @@ import time
import unittest import unittest
import slapos.libnetworkcache import slapos.libnetworkcache
import slapos.signature import slapos.signature
import sys
class NCHandler(BaseHTTPServer.BaseHTTPRequestHandler): class NCHandler(BaseHTTPServer.BaseHTTPRequestHandler):
...@@ -434,6 +435,128 @@ class OnlineTest(OnlineMixin, unittest.TestCase): ...@@ -434,6 +435,128 @@ 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'
test_data = tempfile.TemporaryFile()
test_string = str(random.random())
test_data.write(test_string)
test_data.seek(0)
key_file = tempfile.NamedTemporaryFile()
key_file.write(self.key)
key_file.flush()
key_file.seek(0)
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)
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'
test_data = tempfile.TemporaryFile()
test_string = str(random.random())
test_data.write(test_string)
test_data.seek(0)
key_file = tempfile.NamedTemporaryFile()
key_file.write(self.key)
key_file.flush()
key_file.seek(0)
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)
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'
test_data = tempfile.TemporaryFile()
test_string = str(random.random())
test_data.write(test_string)
test_data.seek(0)
key_file = tempfile.NamedTemporaryFile()
key_file.write(self.key)
key_file.flush()
key_file.seek(0)
signed_nc = slapos.libnetworkcache.NetworkcacheClient(
self.shacache, self.shadir, key_file.name, [self.certificate])
self.assertEqual(True, signed_nc.upload(self.test_data, key,
urlmd5=urlmd5, file_name=file_name))
try:
# disable openssl during download
signed_nc.openssl = '/doesnotexists'
result = signed_nc.select(key)
except slapos.libnetworkcache.DirectoryNotFound, msg:
# No way to download content from shadir w/o openssl
self.assertEqual(str(msg), 'Could not find a trustable entry.')
def test_select_signed_content_openssl_non_functional(self):
key = 'somekey' + str(random.random())
urlmd5 = str(random.random())
file_name = 'my file'
test_data = tempfile.TemporaryFile()
test_string = str(random.random())
test_data.write(test_string)
test_data.seek(0)
key_file = tempfile.NamedTemporaryFile()
key_file.write(self.key)
key_file.flush()
key_file.seek(0)
signed_nc = slapos.libnetworkcache.NetworkcacheClient(
self.shacache, self.shadir, key_file.name, [self.certificate])
self.assertEqual(True, signed_nc.upload(self.test_data, key,
urlmd5=urlmd5, file_name=file_name))
try:
# disable openssl during download
signed_nc.openssl = sys.executable
result = signed_nc.select(key)
except slapos.libnetworkcache.DirectoryNotFound, msg:
# No way to download content from shadir w/o openssl
self.assertEqual(str(msg), 'Could not find a trustable entry.')
def test_select_no_entries(self):
key = 'somekey' + str(random.random())
urlmd5 = str(random.random())
file_name = 'my file'
test_data = tempfile.TemporaryFile()
test_string = str(random.random())
test_data.write(test_string)
test_data.seek(0)
key_file = tempfile.NamedTemporaryFile()
key_file.write(self.key)
key_file.flush()
key_file.seek(0)
nc = slapos.libnetworkcache.NetworkcacheClient(
self.shacache, self.shadir)
self.assertEqual(True, nc.upload(self.test_data, key, urlmd5=urlmd5,
file_name=file_name))
f = os.path.join(self.tree, 'shadir', key)
# now remove the entry from shacache
open(f, 'w').write(json.dumps([]))
try:
nc.select(key)
except slapos.libnetworkcache.DirectoryNotFound, msg:
# empty content in shadir so usual exception is raised
self.assertEqual(str(msg), 'Could not find a trustable entry.')
def test_select_signed_content_server_hacked(self): def test_select_signed_content_server_hacked(self):
key = 'somekey' + str(random.random()) key = 'somekey' + str(random.random())
urlmd5 = str(random.random()) urlmd5 = str(random.random())
......
...@@ -13,100 +13,35 @@ ...@@ -13,100 +13,35 @@
############################################################################## ##############################################################################
import M2Crypto
import time
import argparse import argparse
import os
import subprocess
import sys import sys
def createPrivateKeyAndCertificateFile(signature_certificate_file, signature_private_key_file, **kw): def generateCertificate(certificate_file, key_file, common_name):
''' It must create a private key and a certificate file. ''' if os.path.lexists(certificate_file):
cur_time = M2Crypto.ASN1.ASN1_UTCTIME() raise ValueError("Certificate %r exists, will not overwrite." %
cur_time.set_time(int(time.time()) - 60*60*24) certificate_file)
expire_time = M2Crypto.ASN1.ASN1_UTCTIME() if os.path.lexists(key_file):
expire_time.set_time(int(time.time()) + 60 * 60 * 24 * 365) raise ValueError("Key %r exists, will not overwrite." %
key_file)
cs_rsa = M2Crypto.RSA.gen_key(1024, 65537, lambda: None) subj = '/CN=%s' % common_name
cs_pk = M2Crypto.EVP.PKey() subprocess.check_call(["openssl", "req", "-x509", "-nodes", "-days", "365",
cs_pk.assign_rsa(cs_rsa) "-subj", subj, "-newkey", "rsa:1024", "-keyout", key_file, "-out",
certificate_file])
cs_cert = M2Crypto.X509.X509()
cs_cert.set_not_before(cur_time)
cs_cert.set_not_after(expire_time)
cs_cert.set_pubkey(cs_pk)
cs_name = M2Crypto.X509.X509_Name() def run(*args):
cs_name.C = kw.get('country', '')
cs_name.SP = kw.get('state_name', '')
cs_name.L = kw.get('locality', '')
cs_name.O = kw.get('organization_name', '')
cs_name.OU = kw.get('organization_unit_name', '')
cs_name.CN = kw.get('common_name', '')
cs_name.Email = kw.get('common_name', '')
cs_cert.set_subject(cs_name)
cs_cert.set_issuer_name(cs_name)
cs_cert.sign(cs_pk, md="sha256")
# Saving...
cs_cert.save_pem(signature_certificate_file)
cs_rsa.save_pem(signature_private_key_file, None)
return cs_cert.as_text()
def parseArgument(*args):
''' Parses command line arguments. '''
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--signature-certificate-file', parser.add_argument('certificate_file', type=str,
default='public.pem', help='Certificate file to generate.')
help='X509 Cetification file.')
parser.add_argument('--signature-private-key-file',
default='private.pem',
help='Signature private key file.')
parser.add_argument('-c','--country',
default='XX',
help='Country Name (2 letter code).')
parser.add_argument('-s', '--state-name',
default='Default Province',
help='State or Province Name (full name).')
parser.add_argument('-l', '--locality-name',
default='Default City',
help='Locality Name (eg, city).')
parser.add_argument('-o', '--organization-name',
default='Default Company Ltd',
help='Organization Name (eg, company).')
parser.add_argument('-ou', '--organization-unit-name',
default='',
help='Organizational Unit Name (eg, section).')
parser.add_argument('-cn', '--common-name',
default='',
help="Common Name (eg, your name or your server's " \
"hostname).")
parser.add_argument('-e', '--email',
default='',
help='Email Address.')
argument_option_instance = parser.parse_args(list(args))
option_dict = {}
for argument_key, argument_value in vars(argument_option_instance
).iteritems():
if argument_value is not None:
option_dict.update({argument_key: argument_value})
return option_dict
parser.add_argument('key_file', type=str,
help='Key file to generate.')
parser.add_argument('common_name', type=str, help="Common Name")
def run(): option = parser.parse_args(list(args) or sys.argv[1:])
''' Validate the parameters and call the function to create generateCertificate(option.certificate_file,
private key and certificate file. option.key_file, option.common_name)
'''
option_dict = parseArgument(*sys.argv[1:])
print createPrivateKeyAndCertificateFile(**option_dict)
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