Merge "Replace openssl calls with cryptography lib"
This commit is contained in:
commit
6039044592
154
nova/crypto.py
154
nova/crypto.py
|
@ -25,9 +25,13 @@ from __future__ import absolute_import
|
||||||
import base64
|
import base64
|
||||||
import binascii
|
import binascii
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import struct
|
|
||||||
|
|
||||||
|
from cryptography import exceptions
|
||||||
|
from cryptography.hazmat import backends
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import padding
|
||||||
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
from cryptography import x509
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
@ -35,8 +39,6 @@ from oslo_utils import excutils
|
||||||
from oslo_utils import fileutils
|
from oslo_utils import fileutils
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
import paramiko
|
import paramiko
|
||||||
from pyasn1.codec.der import encoder as der_encoder
|
|
||||||
from pyasn1.type import univ
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from nova import context
|
from nova import context
|
||||||
|
@ -130,24 +132,21 @@ def ensure_ca_filesystem():
|
||||||
|
|
||||||
def generate_fingerprint(public_key):
|
def generate_fingerprint(public_key):
|
||||||
try:
|
try:
|
||||||
parts = public_key.split(' ')
|
pub_bytes = public_key.encode('utf-8')
|
||||||
ssh_alg = parts[0]
|
# Test that the given public_key string is a proper ssh key. The
|
||||||
pub_data = base64.b64decode(parts[1])
|
# returned object is unused since pyca/cryptography does not have a
|
||||||
if ssh_alg == 'ssh-rsa':
|
# fingerprint method.
|
||||||
pkey = paramiko.RSAKey(data=pub_data)
|
serialization.load_ssh_public_key(
|
||||||
elif ssh_alg == 'ssh-dss':
|
pub_bytes, backends.default_backend())
|
||||||
pkey = paramiko.DSSKey(data=pub_data)
|
pub_data = base64.b64decode(public_key.split(' ')[1])
|
||||||
elif ssh_alg == 'ecdsa-sha2-nistp256':
|
digest = hashes.Hash(hashes.MD5(), backends.default_backend())
|
||||||
pkey = paramiko.ECDSAKey(data=pub_data, validate_point=False)
|
digest.update(pub_data)
|
||||||
else:
|
md5hash = digest.finalize()
|
||||||
raise exception.InvalidKeypair(
|
raw_fp = binascii.hexlify(md5hash)
|
||||||
reason=_('Unknown ssh key type %s') % ssh_alg)
|
|
||||||
raw_fp = binascii.hexlify(pkey.get_fingerprint())
|
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
raw_fp = raw_fp.decode('ascii')
|
raw_fp = raw_fp.decode('ascii')
|
||||||
return ':'.join(a + b for a, b in zip(raw_fp[::2], raw_fp[1::2]))
|
return ':'.join(a + b for a, b in zip(raw_fp[::2], raw_fp[1::2]))
|
||||||
except (TypeError, IndexError, UnicodeDecodeError, binascii.Error,
|
except Exception:
|
||||||
paramiko.ssh_exception.SSHException):
|
|
||||||
raise exception.InvalidKeypair(
|
raise exception.InvalidKeypair(
|
||||||
reason=_('failed to generate fingerprint'))
|
reason=_('failed to generate fingerprint'))
|
||||||
|
|
||||||
|
@ -156,12 +155,13 @@ def generate_x509_fingerprint(pem_key):
|
||||||
try:
|
try:
|
||||||
if isinstance(pem_key, six.text_type):
|
if isinstance(pem_key, six.text_type):
|
||||||
pem_key = pem_key.encode('utf-8')
|
pem_key = pem_key.encode('utf-8')
|
||||||
(out, _err) = utils.execute('openssl', 'x509', '-inform', 'PEM',
|
cert = x509.load_pem_x509_certificate(
|
||||||
'-fingerprint', '-noout',
|
pem_key, backends.default_backend())
|
||||||
process_input=pem_key)
|
raw_fp = binascii.hexlify(cert.fingerprint(hashes.SHA1()))
|
||||||
fingerprint = out.rpartition('=')[2].strip()
|
if six.PY3:
|
||||||
return fingerprint.lower()
|
raw_fp = raw_fp.decode('ascii')
|
||||||
except processutils.ProcessExecutionError as ex:
|
return ':'.join(a + b for a, b in zip(raw_fp[::2], raw_fp[1::2]))
|
||||||
|
except (ValueError, TypeError, binascii.Error) as ex:
|
||||||
raise exception.InvalidKeypair(
|
raise exception.InvalidKeypair(
|
||||||
reason=_('failed to generate X509 fingerprint. '
|
reason=_('failed to generate X509 fingerprint. '
|
||||||
'Error message: %s') % ex)
|
'Error message: %s') % ex)
|
||||||
|
@ -189,81 +189,17 @@ def fetch_crl(project_id):
|
||||||
|
|
||||||
|
|
||||||
def decrypt_text(project_id, text):
|
def decrypt_text(project_id, text):
|
||||||
private_key = key_path(project_id)
|
private_key_file = key_path(project_id)
|
||||||
if not os.path.exists(private_key):
|
if not os.path.exists(private_key_file):
|
||||||
raise exception.ProjectNotFound(project_id=project_id)
|
raise exception.ProjectNotFound(project_id=project_id)
|
||||||
|
with open(private_key_file, 'rb') as f:
|
||||||
|
data = f.read()
|
||||||
try:
|
try:
|
||||||
dec, _err = utils.execute('openssl',
|
priv_key = serialization.load_pem_private_key(
|
||||||
'rsautl',
|
data, None, backends.default_backend())
|
||||||
'-decrypt',
|
return priv_key.decrypt(text, padding.PKCS1v15())
|
||||||
'-inkey', '%s' % private_key,
|
except (ValueError, TypeError, exceptions.UnsupportedAlgorithm) as exc:
|
||||||
process_input=text,
|
raise exception.DecryptionFailure(reason=six.text_type(exc))
|
||||||
binary=True)
|
|
||||||
return dec
|
|
||||||
except processutils.ProcessExecutionError as exc:
|
|
||||||
raise exception.DecryptionFailure(reason=exc.stderr)
|
|
||||||
|
|
||||||
|
|
||||||
_RSA_OID = univ.ObjectIdentifier('1.2.840.113549.1.1.1')
|
|
||||||
|
|
||||||
|
|
||||||
def _to_sequence(*vals):
|
|
||||||
seq = univ.Sequence()
|
|
||||||
for i in range(len(vals)):
|
|
||||||
seq.setComponentByPosition(i, vals[i])
|
|
||||||
return seq
|
|
||||||
|
|
||||||
|
|
||||||
def convert_from_sshrsa_to_pkcs8(pubkey):
|
|
||||||
"""Convert a ssh public key to openssl format
|
|
||||||
Equivalent to the ssh-keygen's -m option
|
|
||||||
"""
|
|
||||||
# get the second field from the public key file.
|
|
||||||
try:
|
|
||||||
keydata = base64.b64decode(pubkey.split(None)[1])
|
|
||||||
except IndexError:
|
|
||||||
msg = _("Unable to find the key")
|
|
||||||
raise exception.EncryptionFailure(reason=msg)
|
|
||||||
|
|
||||||
# decode the parts of the key
|
|
||||||
parts = []
|
|
||||||
while keydata:
|
|
||||||
dlen = struct.unpack('>I', keydata[:4])[0]
|
|
||||||
data = keydata[4:dlen + 4]
|
|
||||||
keydata = keydata[4 + dlen:]
|
|
||||||
parts.append(data)
|
|
||||||
|
|
||||||
# Use asn to build the openssl key structure
|
|
||||||
#
|
|
||||||
# SEQUENCE(2 elem)
|
|
||||||
# +- SEQUENCE(2 elem)
|
|
||||||
# | +- OBJECT IDENTIFIER (1.2.840.113549.1.1.1)
|
|
||||||
# | +- NULL
|
|
||||||
# +- BIT STRING(1 elem)
|
|
||||||
# +- SEQUENCE(2 elem)
|
|
||||||
# +- INTEGER(2048 bit)
|
|
||||||
# +- INTEGER 65537
|
|
||||||
|
|
||||||
# Build the sequence for the bit string
|
|
||||||
n_val = int(binascii.hexlify(parts[2]), 16)
|
|
||||||
e_val = int(binascii.hexlify(parts[1]), 16)
|
|
||||||
pkinfo = _to_sequence(univ.Integer(n_val), univ.Integer(e_val))
|
|
||||||
|
|
||||||
# Convert the sequence into a bit string
|
|
||||||
pklong = int(binascii.hexlify(der_encoder.encode(pkinfo)), 16)
|
|
||||||
pkbitstring = univ.BitString("'00%s'B" % bin(pklong)[2:])
|
|
||||||
|
|
||||||
# Build the key data structure
|
|
||||||
oid = _to_sequence(_RSA_OID, univ.Null())
|
|
||||||
pkcs1_seq = _to_sequence(oid, pkbitstring)
|
|
||||||
pkcs8 = base64.b64encode(der_encoder.encode(pkcs1_seq))
|
|
||||||
if six.PY3:
|
|
||||||
pkcs8 = pkcs8.decode('ascii')
|
|
||||||
|
|
||||||
# Remove the embedded new line and format the key, each line
|
|
||||||
# should be 64 characters long
|
|
||||||
return ('-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n' %
|
|
||||||
re.sub("(.{64})", "\\1\n", pkcs8.replace('\n', ''), re.DOTALL))
|
|
||||||
|
|
||||||
|
|
||||||
def ssh_encrypt_text(ssh_public_key, text):
|
def ssh_encrypt_text(ssh_public_key, text):
|
||||||
|
@ -273,23 +209,13 @@ def ssh_encrypt_text(ssh_public_key, text):
|
||||||
"""
|
"""
|
||||||
if isinstance(text, six.text_type):
|
if isinstance(text, six.text_type):
|
||||||
text = text.encode('utf-8')
|
text = text.encode('utf-8')
|
||||||
with utils.tempdir() as tmpdir:
|
|
||||||
sslkey = os.path.abspath(os.path.join(tmpdir, 'ssl.key'))
|
|
||||||
try:
|
try:
|
||||||
out = convert_from_sshrsa_to_pkcs8(ssh_public_key)
|
pub_bytes = ssh_public_key.encode('utf-8')
|
||||||
with open(sslkey, 'w') as f:
|
pub_key = serialization.load_ssh_public_key(
|
||||||
f.write(out)
|
pub_bytes, backends.default_backend())
|
||||||
enc, _err = utils.execute('openssl',
|
return pub_key.encrypt(text, padding.PKCS1v15())
|
||||||
'rsautl',
|
except Exception as exc:
|
||||||
'-encrypt',
|
raise exception.EncryptionFailure(reason=six.text_type(exc))
|
||||||
'-pubin',
|
|
||||||
'-inkey', sslkey,
|
|
||||||
'-keyform', 'PEM',
|
|
||||||
process_input=text,
|
|
||||||
binary=True)
|
|
||||||
return enc
|
|
||||||
except processutils.ProcessExecutionError as exc:
|
|
||||||
raise exception.EncryptionFailure(reason=exc.stderr)
|
|
||||||
|
|
||||||
|
|
||||||
def revoke_cert(project_id, file_name):
|
def revoke_cert(project_id, file_name):
|
||||||
|
|
|
@ -16,9 +16,10 @@
|
||||||
Tests for Crypto module.
|
Tests for Crypto module.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import base64
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from cryptography.hazmat import backends
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
import mock
|
import mock
|
||||||
from mox3 import mox
|
from mox3 import mox
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
|
@ -257,34 +258,6 @@ e6fCXWECgYEAqgpGvva5kJ1ISgNwnJbwiNw0sOT9BMOsdNZBElf0kJIIy6FMPvap
|
||||||
crypto.ssh_encrypt_text, '', self.text)
|
crypto.ssh_encrypt_text, '', self.text)
|
||||||
|
|
||||||
|
|
||||||
class ConversionTests(test.TestCase):
|
|
||||||
k1 = ("ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA4CqmrxfU7x4sJrubpMNxeglul+d"
|
|
||||||
"ByrsicnvQcHDEjPzdvoz+BaoAG9bjCA5mCeTBIISsVTVXz/hxNeiuBV6LH/UR/c"
|
|
||||||
"27yl53ypN+821ImoexQZcKItdnjJ3gVZlDob1f9+1qDVy63NJ1c+TstkrCTRVeo"
|
|
||||||
"9VyE7RpdSS4UCiBe8Xwk3RkedioFxePrI0Ktc2uASw2G0G2Rl7RN7KZOJbCivfF"
|
|
||||||
"LQMAOu6e+7fYvuE1gxGHHj7dxaBY/ioGOm1W4JmQ1V7AKt19zTBlZKduN8FQMSF"
|
|
||||||
"r35CDlvoWs0+OP8nwlebKNCi/5sdL8qiSLrAcPB4LqdkAf/blNSVA2Yl83/c4lQ"
|
|
||||||
"== test@test")
|
|
||||||
|
|
||||||
k2 = ("-----BEGIN PUBLIC KEY-----\n"
|
|
||||||
"MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEA4CqmrxfU7x4sJrubpMNx\n"
|
|
||||||
"eglul+dByrsicnvQcHDEjPzdvoz+BaoAG9bjCA5mCeTBIISsVTVXz/hxNeiuBV6L\n"
|
|
||||||
"H/UR/c27yl53ypN+821ImoexQZcKItdnjJ3gVZlDob1f9+1qDVy63NJ1c+TstkrC\n"
|
|
||||||
"TRVeo9VyE7RpdSS4UCiBe8Xwk3RkedioFxePrI0Ktc2uASw2G0G2Rl7RN7KZOJbC\n"
|
|
||||||
"ivfFLQMAOu6e+7fYvuE1gxGHHj7dxaBY/ioGOm1W4JmQ1V7AKt19zTBlZKduN8FQ\n"
|
|
||||||
"MSFr35CDlvoWs0+OP8nwlebKNCi/5sdL8qiSLrAcPB4LqdkAf/blNSVA2Yl83/c4\n"
|
|
||||||
"lQIBIw==\n"
|
|
||||||
"-----END PUBLIC KEY-----\n")
|
|
||||||
|
|
||||||
def test_convert_keys(self):
|
|
||||||
result = crypto.convert_from_sshrsa_to_pkcs8(self.k1)
|
|
||||||
self.assertEqual(result, self.k2)
|
|
||||||
|
|
||||||
def test_convert_failure(self):
|
|
||||||
self.assertRaises(exception.EncryptionFailure,
|
|
||||||
crypto.convert_from_sshrsa_to_pkcs8, '')
|
|
||||||
|
|
||||||
|
|
||||||
class KeyPairTest(test.TestCase):
|
class KeyPairTest(test.TestCase):
|
||||||
rsa_prv = (
|
rsa_prv = (
|
||||||
"-----BEGIN RSA PRIVATE KEY-----\n"
|
"-----BEGIN RSA PRIVATE KEY-----\n"
|
||||||
|
@ -363,19 +336,18 @@ class KeyPairTest(test.TestCase):
|
||||||
|
|
||||||
def test_generate_key_pair_2048_bits(self):
|
def test_generate_key_pair_2048_bits(self):
|
||||||
(private_key, public_key, fingerprint) = crypto.generate_key_pair()
|
(private_key, public_key, fingerprint) = crypto.generate_key_pair()
|
||||||
raw_pub = public_key.split(' ')[1]
|
pub_bytes = public_key.encode('utf-8')
|
||||||
if six.PY3:
|
pkey = serialization.load_ssh_public_key(
|
||||||
raw_pub = raw_pub.encode('ascii')
|
pub_bytes, backends.default_backend())
|
||||||
raw_pub = base64.b64decode(raw_pub)
|
self.assertEqual(2048, pkey.key_size)
|
||||||
pkey = paramiko.rsakey.RSAKey(None, raw_pub)
|
|
||||||
self.assertEqual(2048, pkey.get_bits())
|
|
||||||
|
|
||||||
def test_generate_key_pair_1024_bits(self):
|
def test_generate_key_pair_1024_bits(self):
|
||||||
bits = 1024
|
bits = 1024
|
||||||
(private_key, public_key, fingerprint) = crypto.generate_key_pair(bits)
|
(private_key, public_key, fingerprint) = crypto.generate_key_pair(bits)
|
||||||
raw_pub = base64.b64decode(public_key.split(' ')[1])
|
pub_bytes = public_key.encode('utf-8')
|
||||||
pkey = paramiko.rsakey.RSAKey(None, raw_pub)
|
pkey = serialization.load_ssh_public_key(
|
||||||
self.assertEqual(bits, pkey.get_bits())
|
pub_bytes, backends.default_backend())
|
||||||
|
self.assertEqual(bits, pkey.key_size)
|
||||||
|
|
||||||
def test_generate_key_pair_mocked_private_key(self):
|
def test_generate_key_pair_mocked_private_key(self):
|
||||||
keyin = six.StringIO()
|
keyin = six.StringIO()
|
||||||
|
|
|
@ -12,6 +12,7 @@ keystonemiddleware>=2.0.0
|
||||||
lxml>=2.3
|
lxml>=2.3
|
||||||
Routes!=2.0,!=2.1,>=1.12.3;python_version=='2.7'
|
Routes!=2.0,!=2.1,>=1.12.3;python_version=='2.7'
|
||||||
Routes!=2.0,>=1.12.3;python_version!='2.7'
|
Routes!=2.0,>=1.12.3;python_version!='2.7'
|
||||||
|
cryptography>=0.9.1 # Apache-2.0
|
||||||
WebOb>=1.2.3
|
WebOb>=1.2.3
|
||||||
greenlet>=0.3.2
|
greenlet>=0.3.2
|
||||||
PasteDeploy>=1.5.0
|
PasteDeploy>=1.5.0
|
||||||
|
@ -21,7 +22,6 @@ sqlalchemy-migrate>=0.9.6
|
||||||
netaddr>=0.7.12
|
netaddr>=0.7.12
|
||||||
netifaces>=0.10.4
|
netifaces>=0.10.4
|
||||||
paramiko>=1.13.0
|
paramiko>=1.13.0
|
||||||
pyasn1
|
|
||||||
Babel>=1.3
|
Babel>=1.3
|
||||||
iso8601>=0.1.9
|
iso8601>=0.1.9
|
||||||
jsonschema!=2.5.0,<3.0.0,>=2.0.0
|
jsonschema!=2.5.0,<3.0.0,>=2.0.0
|
||||||
|
|
Loading…
Reference in New Issue