replace ssh-keygen -m with a python equivalent

When running on latest released versions of Debian and RHEL/CentOS
we get Encryption failure with "ssh-keygen: illegal option -- m"

Fixes LP# 1102501

Change-Id: Ia54bf8f3e8d51c8baa09ba67d2e18ad214316989
NOTE: new dependency on pyasn1 python module
This commit is contained in:
Davanum Srinivas 2013-02-04 22:35:09 -05:00
parent 139d15a405
commit 8d3933d3a1
3 changed files with 98 additions and 11 deletions

View File

@ -24,9 +24,15 @@ Includes root and intermediate CAs, SSH key_pairs and x509 certificates.
from __future__ import absolute_import
import base64
import hashlib
import os
import re
import string
import struct
from pyasn1.codec.der import encoder as der_encoder
from pyasn1.type import univ
from nova import context
from nova import db
@ -181,23 +187,75 @@ def decrypt_text(project_id, text):
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 = eval(
'0x' + ''.join(['%02X' % struct.unpack('B', x)[0] for x in parts[2]]))
e_val = eval(
'0x' + ''.join(['%02X' % struct.unpack('B', x)[0] for x in parts[1]]))
pkinfo = _to_sequence(univ.Integer(n_val), univ.Integer(e_val))
# Convert the sequence into a bit string
pklong = long(der_encoder.encode(pkinfo).encode('hex'), 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.encodestring(der_encoder.encode(pkcs1_seq))
# 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):
"""Encrypt text with an ssh public key.
Requires recent ssh-keygen binary in addition to openssl binary.
"""
with utils.tempdir() as tmpdir:
sshkey = os.path.abspath(os.path.join(tmpdir, 'ssh.key'))
with open(sshkey, 'w') as f:
f.write(ssh_public_key)
sslkey = os.path.abspath(os.path.join(tmpdir, 'ssl.key'))
try:
# NOTE(vish): -P is to skip prompt on bad keys
out, _err = utils.execute('ssh-keygen',
'-P', '',
'-e',
'-f', sshkey,
'-m', 'PKCS8')
out = convert_from_sshrsa_to_pkcs8(ssh_public_key)
with open(sslkey, 'w') as f:
f.write(out)
enc, _err = utils.execute('openssl',

View File

@ -212,3 +212,31 @@ e6fCXWECgYEAqgpGvva5kJ1ISgNwnJbwiNw0sOT9BMOsdNZBElf0kJIIy6FMPvap
def test_ssh_encrypt_failure(self):
self.assertRaises(exception.EncryptionFailure,
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, '')

View File

@ -16,6 +16,7 @@ sqlalchemy-migrate>=0.7.2
netaddr
suds==0.4
paramiko
pyasn1
Babel>=0.9.6
iso8601>=0.1.4
httplib2