187 lines
6.5 KiB
Python
187 lines
6.5 KiB
Python
# authentic2 - versatile identity manager
|
|
# Copyright (C) 2010-2019 Entr'ouvert
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU Affero General Public License as published
|
|
# by the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import base64
|
|
import binascii
|
|
import tempfile
|
|
import os
|
|
import subprocess
|
|
import six
|
|
|
|
_openssl = 'openssl'
|
|
|
|
|
|
def decapsulate_pem_file(file_or_string):
|
|
'''Remove PEM header lines'''
|
|
if not isinstance(file_or_string, six.string_types):
|
|
content = file_or_string.read()
|
|
else:
|
|
content = file_or_string
|
|
i = content.find('--BEGIN')
|
|
j = content.find('\n', i)
|
|
k = content.find('--END', j)
|
|
l = content.rfind('\n', 0, k) # noqa: E741
|
|
return content[j + 1:l]
|
|
|
|
|
|
def _call_openssl(args):
|
|
'''Use subprocees to spawn an openssl process
|
|
|
|
Return a tuple made of the return code and the stdout output
|
|
'''
|
|
try:
|
|
process = subprocess.Popen(args=[_openssl] + args,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
universal_newlines=True)
|
|
output = process.communicate()[0]
|
|
return process.returncode, output
|
|
except OSError:
|
|
return 1, None
|
|
|
|
|
|
def check_key_pair_consistency(publickey=None, privatekey=None):
|
|
'''Check if two PEM key pair whether they are publickey or certificate, are
|
|
well formed and related.
|
|
'''
|
|
if not publickey or not privatekey:
|
|
return None
|
|
|
|
privatekey_file = tempfile.NamedTemporaryFile(mode='w')
|
|
publickey_file = tempfile.NamedTemporaryFile(mode='w')
|
|
with privatekey_file, publickey_file:
|
|
|
|
privatekey_file.write(privatekey)
|
|
privatekey_file.flush()
|
|
publickey_file.write(publickey)
|
|
publickey_file.flush()
|
|
|
|
if 'BEGIN CERTIFICATE' in publickey:
|
|
rc1, modulus1 = _call_openssl(['x509', '-in', publickey_file.name, '-noout', '-modulus'])
|
|
else:
|
|
rc1, modulus1 = _call_openssl(['rsa', '-pubin', '-in', publickey_file.name, '-noout', '-modulus'])
|
|
if rc1 != 0:
|
|
rc1, modulus1 = _call_openssl(['dsa', '-pubin', '-in', publickey_file.name, '-noout', '-modulus'])
|
|
|
|
if rc1 != 0:
|
|
return False
|
|
|
|
rc2, modulus2 = _call_openssl(['rsa', '-in', privatekey_file.name, '-noout', '-modulus'])
|
|
if rc2 != 0:
|
|
rc2, modulus2 = _call_openssl(['dsa', '-in', privatekey_file.name, '-noout', '-modulus'])
|
|
|
|
if rc1 == 0 and rc2 == 0 and modulus1 == modulus2:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
def generate_rsa_keypair(numbits=1024):
|
|
'''Generate simple RSA public and private key files
|
|
'''
|
|
privatekey_file = tempfile.NamedTemporaryFile(mode='r')
|
|
publickey_file = tempfile.NamedTemporaryFile(mode='r')
|
|
|
|
with privatekey_file, publickey_file:
|
|
rc1, _ = _call_openssl(['genrsa', '-out', privatekey_file.name, '-passout', 'pass:', str(numbits)])
|
|
if rc1 != 0:
|
|
raise Exception('Failed to generate a key')
|
|
rc2, _ = _call_openssl(['rsa', '-in', privatekey_file.name, '-pubout', '-out', publickey_file.name])
|
|
if rc2 != 0:
|
|
raise Exception('Failed to generate a key')
|
|
return (publickey_file.read(), privatekey_file.read())
|
|
|
|
|
|
def get_rsa_public_key_modulus(publickey):
|
|
publickey_file = tempfile.NamedTemporaryFile(mode='w')
|
|
|
|
with publickey_file:
|
|
publickey_file.write(publickey)
|
|
publickey_file.flush()
|
|
|
|
if 'BEGIN PUBLIC' in publickey:
|
|
rc, modulus = _call_openssl(['rsa', '-pubin', '-in', publickey_file.name, '-noout', '-modulus'])
|
|
elif 'BEGIN RSA PRIVATE KEY' in publickey:
|
|
rc, modulus = _call_openssl(['rsa', '-in', publickey_file.name, '-noout', '-modulus'])
|
|
elif 'BEGIN CERTIFICATE' in publickey:
|
|
rc, modulus = _call_openssl(['x509', '-in', publickey_file.name, '-noout', '-modulus'])
|
|
else:
|
|
return None
|
|
|
|
i = modulus.find('=')
|
|
|
|
if rc == 0 and i:
|
|
return int(modulus[i + 1:].strip(), 16)
|
|
return None
|
|
|
|
|
|
def get_rsa_public_key_exponent(publickey):
|
|
publickey_file = tempfile.NamedTemporaryFile(mode='w')
|
|
|
|
with publickey_file:
|
|
publickey_file.write(publickey)
|
|
publickey_file.flush()
|
|
|
|
_exponent = 'Exponent: '
|
|
if 'BEGIN PUBLIC' in publickey:
|
|
rc, modulus = _call_openssl(['rsa', '-pubin', '-in', publickey_file.name, '-noout', '-text'])
|
|
elif 'BEGIN RSA PRIVATE' in publickey:
|
|
rc, modulus = _call_openssl(['rsa', '-in', publickey_file.name, '-noout', '-text'])
|
|
_exponent = 'publicExponent: '
|
|
elif 'BEGIN CERTIFICATE' in publickey:
|
|
rc, modulus = _call_openssl(['x509', '-in', publickey_file.name, '-noout', '-text'])
|
|
else:
|
|
return None
|
|
i = modulus.find(_exponent)
|
|
j = modulus.find('(', i)
|
|
if rc == 0 and i and j:
|
|
return int(modulus[i + len(_exponent):j].strip())
|
|
return None
|
|
|
|
|
|
def can_generate_rsa_key_pair():
|
|
syspath = os.environ.get('PATH')
|
|
if syspath:
|
|
for base in syspath.split(':'):
|
|
if os.path.exists(os.path.join(base, 'openssl')):
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
def get_xmldsig_rsa_key_value(publickey):
|
|
def int_to_bin(i):
|
|
h = hex(i)[2:].strip('L')
|
|
if len(h) % 2 == 1:
|
|
h = '0' + h
|
|
return binascii.unhexlify(h)
|
|
|
|
mod = get_rsa_public_key_modulus(publickey)
|
|
exp = get_rsa_public_key_exponent(publickey)
|
|
mod_byte_length = (mod.bit_length() + 7) // 8
|
|
exp_byte_length = (exp.bit_length() + 7) // 8
|
|
mod_bytes = mod.to_bytes(mod_byte_length, byteorder='big')
|
|
exp_bytes = exp.to_bytes(exp_byte_length, byteorder='big')
|
|
mod_cryptobinary = base64.b64encode(mod_bytes).decode('ascii')
|
|
exp_cryptobinary = base64.b64encode(exp_bytes).decode('ascii')
|
|
return (
|
|
'<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">\n\t'
|
|
'<Modulus>%s</Modulus>\n\t'
|
|
'<Exponent>%s</Exponent>\n</RSAKeyValue>' % (
|
|
mod_cryptobinary,
|
|
exp_cryptobinary)
|
|
)
|