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:
Brian Waldon 2012-01-31 20:50:48 -08:00
parent 2be2d0778c
commit 3759bcf3fc
8 changed files with 74 additions and 221 deletions

View File

@ -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::

View File

@ -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

View File

@ -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):

View File

@ -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')

View File

@ -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)

View File

@ -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',

View File

@ -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")

View File

@ -31,3 +31,4 @@ coverage
nosexcover
paramiko
feedparser
pycrypto