Excise M2Crypto!
This required rewriting our Diffie-Hellman-Merkle implementation for set_admin_password in xen. Fixes bug 917851. Change-Id: Ic4cdcc06221f003aec2dcd5ba05a1a9ad19d39c9
This commit is contained in:
parent
2be2d0778c
commit
3759bcf3fc
@ -54,22 +54,16 @@ Install the prerequisite packages.
|
||||
|
||||
On Ubuntu::
|
||||
|
||||
sudo apt-get install python-dev swig libssl-dev python-pip git-core
|
||||
sudo apt-get install python-dev libssl-dev python-pip git-core
|
||||
|
||||
On Fedora-based distributions (e.g., Fedora/RHEL/CentOS/Scientific Linux)::
|
||||
|
||||
sudo yum install python-devel swig openssl-devel python-pip git
|
||||
sudo yum install python-devel openssl-devel python-pip git
|
||||
|
||||
|
||||
Mac OS X Systems
|
||||
----------------
|
||||
|
||||
Install swig, which is needed to build the M2Crypto Python package. If you are
|
||||
using the `homebrew <http://mxcl.github.com/homebrew/>`_, package manager,
|
||||
install swig by doing::
|
||||
|
||||
brew install swig
|
||||
|
||||
Install virtualenv::
|
||||
|
||||
sudo easy_install virtualenv
|
||||
@ -120,7 +114,7 @@ You can manually install the virtual environment instead of having
|
||||
|
||||
This will install all of the Python packages listed in the
|
||||
``tools/pip-requires`` file into your virtualenv. There will also be some
|
||||
additional packages (pip, distribute, greenlet, M2Crypto) that are installed
|
||||
additional packages (pip, distribute, greenlet) that are installed
|
||||
by the ``tools/install_venv.py`` file into the virutalenv.
|
||||
|
||||
If all goes well, you should get a message something like this::
|
||||
|
105
nova/crypto.py
105
nova/crypto.py
@ -28,12 +28,10 @@ import hashlib
|
||||
import os
|
||||
import shutil
|
||||
import string
|
||||
import struct
|
||||
import tempfile
|
||||
import time
|
||||
import utils
|
||||
|
||||
import M2Crypto
|
||||
import Crypto.Cipher.AES
|
||||
|
||||
gettext.install('nova', unicode=1)
|
||||
|
||||
@ -154,33 +152,10 @@ def generate_key_pair(bits=1024):
|
||||
public_key = open(keyfile + '.pub').read()
|
||||
|
||||
shutil.rmtree(tmpdir)
|
||||
# code below returns public key in pem format
|
||||
# key = M2Crypto.RSA.gen_key(bits, 65537, callback=lambda: None)
|
||||
# private_key = key.as_pem(cipher=None)
|
||||
# bio = M2Crypto.BIO.MemoryBuffer()
|
||||
# key.save_pub_key_bio(bio)
|
||||
# public_key = bio.read()
|
||||
# public_key, err = execute('ssh-keygen', '-y', '-f',
|
||||
# '/dev/stdin', private_key)
|
||||
|
||||
return (private_key, public_key, fingerprint)
|
||||
|
||||
|
||||
def ssl_pub_to_ssh_pub(ssl_public_key, name='root', suffix='nova'):
|
||||
buf = M2Crypto.BIO.MemoryBuffer(ssl_public_key)
|
||||
rsa_key = M2Crypto.RSA.load_pub_key_bio(buf)
|
||||
e, n = rsa_key.pub()
|
||||
|
||||
key_type = 'ssh-rsa'
|
||||
|
||||
key_data = struct.pack('>I', len(key_type))
|
||||
key_data += key_type
|
||||
key_data += '%s%s' % (e, n)
|
||||
|
||||
b64_blob = base64.b64encode(key_data)
|
||||
return '%s %s %s@%s\n' % (key_type, b64_blob, name, suffix)
|
||||
|
||||
|
||||
def fetch_crl(project_id):
|
||||
"""Get crl file for project."""
|
||||
if not FLAGS.use_project_ca:
|
||||
@ -335,72 +310,22 @@ def _sign_csr(csr_text, ca_folder):
|
||||
return (serial, crtfile.read())
|
||||
|
||||
|
||||
def mkreq(bits, subject='foo', ca=0):
|
||||
pk = M2Crypto.EVP.PKey()
|
||||
req = M2Crypto.X509.Request()
|
||||
rsa = M2Crypto.RSA.gen_key(bits, 65537, callback=lambda: None)
|
||||
pk.assign_rsa(rsa)
|
||||
rsa = None # should not be freed here
|
||||
req.set_pubkey(pk)
|
||||
req.set_subject(subject)
|
||||
req.sign(pk, 'sha512')
|
||||
assert req.verify(pk)
|
||||
pk2 = req.get_pubkey()
|
||||
assert req.verify(pk2)
|
||||
return req, pk
|
||||
|
||||
|
||||
def mkcacert(subject='nova', years=1):
|
||||
req, pk = mkreq(2048, subject, ca=1)
|
||||
pkey = req.get_pubkey()
|
||||
sub = req.get_subject()
|
||||
cert = M2Crypto.X509.X509()
|
||||
cert.set_serial_number(1)
|
||||
cert.set_version(2)
|
||||
# FIXME subject is not set in mkreq yet
|
||||
cert.set_subject(sub)
|
||||
t = long(time.time()) + time.timezone
|
||||
now = M2Crypto.ASN1.ASN1_UTCTIME()
|
||||
now.set_time(t)
|
||||
nowPlusYear = M2Crypto.ASN1.ASN1_UTCTIME()
|
||||
nowPlusYear.set_time(t + (years * 60 * 60 * 24 * 365))
|
||||
cert.set_not_before(now)
|
||||
cert.set_not_after(nowPlusYear)
|
||||
issuer = M2Crypto.X509.X509_Name()
|
||||
issuer.C = 'US'
|
||||
issuer.CN = subject
|
||||
cert.set_issuer(issuer)
|
||||
cert.set_pubkey(pkey)
|
||||
ext = M2Crypto.X509.new_extension('basicConstraints', 'CA:TRUE')
|
||||
cert.add_ext(ext)
|
||||
cert.sign(pk, 'sha512')
|
||||
|
||||
# print 'cert', dir(cert)
|
||||
print cert.as_pem()
|
||||
print pk.get_rsa().as_pem()
|
||||
|
||||
return cert, pk, pkey
|
||||
|
||||
|
||||
def _build_cipher(key, iv, encode=True):
|
||||
def _build_cipher(key, iv):
|
||||
"""Make a 128bit AES CBC encode/decode Cipher object.
|
||||
Padding is handled internally."""
|
||||
operation = 1 if encode else 0
|
||||
return M2Crypto.EVP.Cipher(alg='aes_128_cbc', key=key, iv=iv, op=operation)
|
||||
return Crypto.Cipher.AES.new(key, IV=iv)
|
||||
|
||||
|
||||
def encryptor(key, iv=None):
|
||||
def encryptor(key):
|
||||
"""Simple symmetric key encryption."""
|
||||
key = base64.b64decode(key)
|
||||
if iv is None:
|
||||
iv = '\0' * 16
|
||||
else:
|
||||
iv = base64.b64decode(iv)
|
||||
iv = '\0' * 16
|
||||
|
||||
def encrypt(data):
|
||||
cipher = _build_cipher(key, iv, encode=True)
|
||||
v = cipher.update(data)
|
||||
v = v + cipher.final()
|
||||
cipher = _build_cipher(key, iv)
|
||||
# Must pad string to multiple of 16 chars
|
||||
padding = (16 - len(data) % 16) * " "
|
||||
v = cipher.encrypt(data + padding)
|
||||
del cipher
|
||||
v = base64.b64encode(v)
|
||||
return v
|
||||
@ -408,19 +333,15 @@ def encryptor(key, iv=None):
|
||||
return encrypt
|
||||
|
||||
|
||||
def decryptor(key, iv=None):
|
||||
def decryptor(key):
|
||||
"""Simple symmetric key decryption."""
|
||||
key = base64.b64decode(key)
|
||||
if iv is None:
|
||||
iv = '\0' * 16
|
||||
else:
|
||||
iv = base64.b64decode(iv)
|
||||
iv = '\0' * 16
|
||||
|
||||
def decrypt(data):
|
||||
data = base64.b64decode(data)
|
||||
cipher = _build_cipher(key, iv, encode=False)
|
||||
v = cipher.update(data)
|
||||
v = v + cipher.final()
|
||||
cipher = _build_cipher(key, iv)
|
||||
v = cipher.decrypt(data).rstrip()
|
||||
del cipher
|
||||
return v
|
||||
|
||||
|
@ -22,8 +22,6 @@ Weighing Functions.
|
||||
import json
|
||||
import operator
|
||||
|
||||
import M2Crypto
|
||||
|
||||
from novaclient import v1_1 as novaclient
|
||||
from novaclient import exceptions as novaclient_exceptions
|
||||
from nova import crypto
|
||||
@ -43,11 +41,6 @@ FLAGS = flags.FLAGS
|
||||
LOG = logging.getLogger('nova.scheduler.distributed_scheduler')
|
||||
|
||||
|
||||
class InvalidBlob(exception.NovaException):
|
||||
message = _("Ill-formed or incorrectly routed 'blob' data sent "
|
||||
"to instance create request.")
|
||||
|
||||
|
||||
class DistributedScheduler(driver.Scheduler):
|
||||
"""Scheduler that can work across any nova deployment, from simple
|
||||
deployments to multiple nested zones.
|
||||
@ -185,19 +178,16 @@ class DistributedScheduler(driver.Scheduler):
|
||||
or None if invalid. Broken out for testing.
|
||||
"""
|
||||
decryptor = crypto.decryptor(FLAGS.build_plan_encryption_key)
|
||||
try:
|
||||
json_entry = decryptor(blob)
|
||||
# Extract our WeightedHost values
|
||||
wh_dict = json.loads(json_entry)
|
||||
host = wh_dict.get('host', None)
|
||||
blob = wh_dict.get('blob', None)
|
||||
zone = wh_dict.get('zone', None)
|
||||
return least_cost.WeightedHost(wh_dict['weight'],
|
||||
host_state=host_manager.HostState(host, 'compute'),
|
||||
blob=blob, zone=zone)
|
||||
json_entry = decryptor(blob)
|
||||
|
||||
except M2Crypto.EVP.EVPError:
|
||||
raise InvalidBlob()
|
||||
# Extract our WeightedHost values
|
||||
wh_dict = json.loads(json_entry)
|
||||
host = wh_dict.get('host', None)
|
||||
blob = wh_dict.get('blob', None)
|
||||
zone = wh_dict.get('zone', None)
|
||||
return least_cost.WeightedHost(wh_dict['weight'],
|
||||
host_state=host_manager.HostState(host, 'compute'),
|
||||
blob=blob, zone=zone)
|
||||
|
||||
def _ask_child_zone_to_create_instance(self, context, weighted_host,
|
||||
request_spec, kwargs):
|
||||
|
@ -20,18 +20,15 @@
|
||||
import base64
|
||||
import copy
|
||||
import functools
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
from M2Crypto import BIO
|
||||
from M2Crypto import RSA
|
||||
|
||||
from nova.api.ec2 import cloud
|
||||
from nova.api.ec2 import ec2utils
|
||||
from nova.api.ec2 import inst_state
|
||||
from nova.compute import power_state
|
||||
from nova.compute import vm_states
|
||||
from nova import context
|
||||
from nova import crypto
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
@ -1188,16 +1185,21 @@ class CloudTestCase(test.TestCase):
|
||||
def test_key_generation(self):
|
||||
result = self._create_key('test')
|
||||
private_key = result['private_key']
|
||||
key = RSA.load_key_string(private_key, callback=lambda: None)
|
||||
bio = BIO.MemoryBuffer()
|
||||
public_key = db.key_pair_get(self.context,
|
||||
|
||||
expected = db.key_pair_get(self.context,
|
||||
self.context.user_id,
|
||||
'test')['public_key']
|
||||
key.save_pub_key_bio(bio)
|
||||
converted = crypto.ssl_pub_to_ssh_pub(bio.read())
|
||||
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
os.write(fd, private_key)
|
||||
|
||||
public_key, err = utils.execute('ssh-keygen', '-e', '-f', fname)
|
||||
|
||||
os.unlink(fname)
|
||||
|
||||
# assert key fields are equal
|
||||
self.assertEqual(public_key.split(" ")[1].strip(),
|
||||
converted.split(" ")[1].strip())
|
||||
self.assertEqual(''.join(public_key.split("\n")[2:-2]),
|
||||
expected.split(" ")[1].strip())
|
||||
|
||||
def test_describe_key_pairs(self):
|
||||
self._create_key('test1')
|
||||
|
@ -21,7 +21,6 @@ import shutil
|
||||
import tempfile
|
||||
|
||||
import mox
|
||||
from M2Crypto import X509
|
||||
|
||||
from nova import crypto
|
||||
from nova import db
|
||||
@ -48,17 +47,6 @@ class SymmetricKeyTestCase(test.TestCase):
|
||||
|
||||
self.assertEquals(plain_text, plain)
|
||||
|
||||
# IV supplied ...
|
||||
iv = '562e17996d093d28ddb3ba695a2e6f58'
|
||||
encrypt = crypto.encryptor(key, iv)
|
||||
cipher_text = encrypt(plain_text)
|
||||
self.assertNotEquals(plain_text, cipher_text)
|
||||
|
||||
decrypt = crypto.decryptor(key, iv)
|
||||
plain = decrypt(cipher_text)
|
||||
|
||||
self.assertEquals(plain_text, plain)
|
||||
|
||||
|
||||
class X509Test(test.TestCase):
|
||||
def test_can_generate_x509(self):
|
||||
@ -69,18 +57,19 @@ class X509Test(test.TestCase):
|
||||
_key, cert_str = crypto.generate_x509_cert('fake', 'fake')
|
||||
|
||||
project_cert = crypto.fetch_ca(project_id='fake')
|
||||
cloud_cert = crypto.fetch_ca()
|
||||
# TODO(vish): This will need to be replaced with something else
|
||||
# when we remove M2Crypto
|
||||
signed_cert = X509.load_cert_string(cert_str)
|
||||
project_cert = X509.load_cert_string(project_cert)
|
||||
cloud_cert = X509.load_cert_string(cloud_cert)
|
||||
self.assertTrue(signed_cert.verify(project_cert.get_pubkey()))
|
||||
|
||||
if not FLAGS.use_project_ca:
|
||||
self.assertTrue(signed_cert.verify(cloud_cert.get_pubkey()))
|
||||
else:
|
||||
self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey()))
|
||||
signed_cert_file = os.path.join(tmpdir, "signed")
|
||||
with open(signed_cert_file, 'w') as keyfile:
|
||||
keyfile.write(cert_str)
|
||||
|
||||
project_cert_file = os.path.join(tmpdir, "project")
|
||||
with open(project_cert_file, 'w') as keyfile:
|
||||
keyfile.write(project_cert)
|
||||
|
||||
enc, err = utils.execute('openssl', 'verify', '-CAfile',
|
||||
project_cert_file, '-verbose', signed_cert_file)
|
||||
self.assertFalse(err)
|
||||
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
|
@ -20,7 +20,9 @@ Management class for VM-related functions (spawn, reboot, etc).
|
||||
"""
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import json
|
||||
import os
|
||||
import pickle
|
||||
import random
|
||||
import sys
|
||||
@ -28,7 +30,6 @@ import time
|
||||
import uuid
|
||||
|
||||
from eventlet import greenthread
|
||||
import M2Crypto
|
||||
|
||||
from nova.common import cfg
|
||||
from nova.compute import api as compute
|
||||
@ -1763,7 +1764,6 @@ class VMOps(object):
|
||||
"""Removes filters for each VIF of the specified instance."""
|
||||
self.firewall_driver.unfilter_instance(instance_ref,
|
||||
network_info=network_info)
|
||||
########################################################################
|
||||
|
||||
|
||||
class SimpleDH(object):
|
||||
@ -1776,51 +1776,35 @@ class SimpleDH(object):
|
||||
as it uses that to handle the encryption and decryption. If openssl
|
||||
is not available, a RuntimeError will be raised.
|
||||
"""
|
||||
def __init__(self, prime=None, base=None, secret=None):
|
||||
"""
|
||||
You can specify the values for prime and base if you wish;
|
||||
otherwise, reasonable default values will be used.
|
||||
"""
|
||||
if prime is None:
|
||||
self._prime = 162259276829213363391578010288127
|
||||
else:
|
||||
self._prime = prime
|
||||
if base is None:
|
||||
self._base = 5
|
||||
else:
|
||||
self._base = base
|
||||
self._shared = self._public = None
|
||||
def __init__(self):
|
||||
self._prime = 162259276829213363391578010288127
|
||||
self._base = 5
|
||||
self._public = None
|
||||
self._shared = None
|
||||
self.generate_private()
|
||||
|
||||
self._dh = M2Crypto.DH.set_params(
|
||||
self.dec_to_mpi(self._prime),
|
||||
self.dec_to_mpi(self._base))
|
||||
self._dh.gen_key()
|
||||
self._public = self.mpi_to_dec(self._dh.pub)
|
||||
def generate_private(self):
|
||||
self._private = int(binascii.hexlify(os.urandom(10)), 16)
|
||||
return self._private
|
||||
|
||||
def get_public(self):
|
||||
self._public = self.mod_exp(self._base, self._private, self._prime)
|
||||
return self._public
|
||||
|
||||
def compute_shared(self, other):
|
||||
self._shared = self.bin_to_dec(
|
||||
self._dh.compute_key(self.dec_to_mpi(other)))
|
||||
self._shared = self.mod_exp(other, self._private, self._prime)
|
||||
return self._shared
|
||||
|
||||
def mpi_to_dec(self, mpi):
|
||||
bn = M2Crypto.m2.mpi_to_bn(mpi)
|
||||
hexval = M2Crypto.m2.bn_to_hex(bn)
|
||||
dec = int(hexval, 16)
|
||||
return dec
|
||||
|
||||
def bin_to_dec(self, binval):
|
||||
bn = M2Crypto.m2.bin_to_bn(binval)
|
||||
hexval = M2Crypto.m2.bn_to_hex(bn)
|
||||
dec = int(hexval, 16)
|
||||
return dec
|
||||
|
||||
def dec_to_mpi(self, dec):
|
||||
bn = M2Crypto.m2.dec_to_bn('%s' % dec)
|
||||
mpi = M2Crypto.m2.bn_to_mpi(bn)
|
||||
return mpi
|
||||
@staticmethod
|
||||
def mod_exp(num, exp, mod):
|
||||
"""Efficient implementation of (num ** exp) % mod"""
|
||||
result = 1
|
||||
while exp > 0:
|
||||
if (exp & 1) == 1:
|
||||
result = (result * num) % mod
|
||||
exp = exp >> 1
|
||||
num = (num * num) % mod
|
||||
return result
|
||||
|
||||
def _run_ssl(self, text, decrypt=False):
|
||||
cmd = ['openssl', 'aes-128-cbc', '-A', '-a', '-pass',
|
||||
|
@ -27,7 +27,6 @@ import optparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import platform
|
||||
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
@ -88,9 +87,6 @@ class Distro(object):
|
||||
' requires virtualenv, please install it using your'
|
||||
' favorite package management tool')
|
||||
|
||||
def install_m2crypto(self):
|
||||
pip_install('M2Crypto')
|
||||
|
||||
def post_process(self):
|
||||
"""Any distribution-specific post-processing gets done here.
|
||||
|
||||
@ -99,18 +95,6 @@ class Distro(object):
|
||||
pass
|
||||
|
||||
|
||||
class UbuntuOneiric(Distro):
|
||||
"""Oneiric specific installation steps"""
|
||||
|
||||
def install_m2crypto(self):
|
||||
"""
|
||||
The pip installed version of m2crypto has problems on oneiric
|
||||
"""
|
||||
print "Attempting to install 'python-m2crypto' via apt-get"
|
||||
run_command(['sudo', 'apt-get', 'install', '-y',
|
||||
"python-m2crypto"])
|
||||
|
||||
|
||||
class Fedora(Distro):
|
||||
"""This covers all Fedora-based distributions.
|
||||
|
||||
@ -136,14 +120,6 @@ class Fedora(Distro):
|
||||
|
||||
super(Fedora, self).install_virtualenv()
|
||||
|
||||
#
|
||||
# pip install M2Crypto fails on Fedora because of
|
||||
# weird differences with OpenSSL headers
|
||||
#
|
||||
def install_m2crypto(self):
|
||||
if not self.check_pkg('m2crypto'):
|
||||
self.yum_install('m2crypto')
|
||||
|
||||
def post_process(self):
|
||||
"""Workaround for a bug in eventlet.
|
||||
|
||||
@ -169,8 +145,6 @@ def get_distro():
|
||||
if os.path.exists('/etc/fedora-release') or \
|
||||
os.path.exists('/etc/redhat-release'):
|
||||
return Fedora()
|
||||
elif platform.linux_distribution()[2] == 'oneiric':
|
||||
return UbuntuOneiric()
|
||||
else:
|
||||
return Distro()
|
||||
|
||||
@ -215,8 +189,6 @@ def install_dependencies(venv=VENV):
|
||||
|
||||
pip_install('-r', PIP_REQUIRES)
|
||||
|
||||
get_distro().install_m2crypto()
|
||||
|
||||
# Tell the virtual env how to "import nova"
|
||||
pthfile = os.path.join(venv, "lib", PY_VERSION, "site-packages",
|
||||
"nova.pth")
|
||||
|
@ -31,3 +31,4 @@ coverage
|
||||
nosexcover
|
||||
paramiko
|
||||
feedparser
|
||||
pycrypto
|
||||
|
Loading…
Reference in New Issue
Block a user