c837807
# -*- coding: utf-8 -*-
c837807
#
c837807
# Generate OpenSSL configuration file for AusweisApp2 from settings found
c837807
# in the application's 'config.json' file.
c837807
#
c837807
# Copyright (c) 2020 Björn Esser <besser82@fedoraproject.org>
c837807
#
c837807
# Permission is hereby granted, free of charge, to any person obtaining a copy
c837807
# of this software and associated documentation files (the "Software"), to deal
c837807
# in the Software without restriction, including without limitation the rights
c837807
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
c837807
# copies of the Software, and to permit persons to whom the Software is
c837807
# furnished to do so, subject to the following conditions:
c837807
#
c837807
# The above copyright notice and this permission notice shall be included in all
c837807
# copies or substantial portions of the Software.
c837807
#
c837807
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
c837807
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
c837807
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
c837807
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
c837807
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
c837807
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
c837807
# SOFTWARE.
c837807
c837807
c837807
import json, sys
c837807
c837807
c837807
def constant(f):
c837807
    def fset(self, value):
c837807
        raise TypeError
c837807
    def fget(self):
c837807
        return f()
c837807
    return property(fget, fset)
c837807
c837807
c837807
class _Const(object):
c837807
    @constant
c837807
    def CONF_OPTIONS():
c837807
        return [
c837807
                   'ciphers',
c837807
                   'ellipticCurves',
c837807
                   'signatureAlgorithms',
c837807
               ]
c837807
c837807
    @constant
c837807
    def CONF_SECTIONS():
c837807
        return [
c837807
                   'tlsSettings',
c837807
                   'tlsSettingsPsk',
c837807
                   'tlsSettingsRemoteReader',
c837807
                   'tlsSettingsRemoteReaderPairing',
c837807
               ]
c837807
c837807
    @constant
c837807
    def DEFAULT_CIPHERS_TLS13():
c837807
        return [
c837807
                   'TLS_AES_256_GCM_SHA384',
c837807
                   'TLS_AES_128_GCM_SHA256',
c837807
               ]
c837807
c837807
    @constant
c837807
    def KEYSIZE_EC_OPTION():
c837807
        return 'Ec'
c837807
c837807
    @constant
c837807
    def KEYSIZE_OPTIONS():
c837807
        return [
c837807
                   'Rsa',
c837807
                   'Dsa',
c837807
                   'Dh',
c837807
               ]
c837807
c837807
    @constant
c837807
    def KEYSIZE_SECTIONS():
c837807
        return [
c837807
                   'minStaticKeySizes',
c837807
                   'minEphemeralKeySizes',
c837807
               ]
c837807
c837807
    @constant
c837807
    def TLS_VERSIONS():
c837807
        return {
c837807
                   'TlsV1_2': (2, 'TLSv1.2'),
c837807
                   'TlsV1_3': (3, 'TLSv1.3'),
c837807
               }
c837807
c837807
c837807
CONST = _Const()
c837807
c837807
c837807
def get_min_ssl_sec_level(json_data):
c837807
    sec_level = 0
c837807
    min_keysize = sys.maxsize
c837807
    min_ecsize = sys.maxsize
c837807
    for section in CONST.KEYSIZE_SECTIONS:
c837807
        if section in json_data:
c837807
            for option in CONST.KEYSIZE_OPTIONS:
c837807
                if option in json_data[section]:
c837807
                    if min_keysize > json_data[section][option]:
c837807
                        min_keysize = json_data[section][option]
c837807
            if CONST.KEYSIZE_EC_OPTION in json_data[section]:
c837807
                    if min_ecsize > json_data[section][CONST.KEYSIZE_EC_OPTION]:
c837807
                        min_ecsize = json_data[section][CONST.KEYSIZE_EC_OPTION]
c837807
c837807
    if min_keysize >=  1000 and min_ecsize >= 160:
c837807
        sec_level = 1
c837807
    if min_keysize >=  2000 and min_ecsize >= 224:
c837807
        sec_level = 2
c837807
    if min_keysize >=  3000 and min_ecsize >= 256:
c837807
        sec_level = 3
c837807
    if min_keysize >=  7000 and min_ecsize >= 384:
c837807
        sec_level = 4
c837807
    if min_keysize >= 15000 and min_ecsize >= 512:
c837807
        sec_level = 5
c837807
c837807
    return sec_level
c837807
c837807
c837807
def get_proto_ver(json_data):
c837807
    conf_dict = {
c837807
                    'minProtocolVersion': list(CONST.TLS_VERSIONS.keys())[-1],
c837807
                    'maxProtocolVersion': list(CONST.TLS_VERSIONS.keys())[0],
c837807
                }
c837807
    for section in CONST.CONF_SECTIONS:
c837807
        if section in json_data:
c837807
            if 'protocolVersion' in json_data[section]:
c837807
                have = conf_dict['minProtocolVersion']
c837807
                want = json_data[section]['protocolVersion']
c837807
                if CONST.TLS_VERSIONS[want][0] < CONST.TLS_VERSIONS[have][0]:
c837807
                    conf_dict['minProtocolVersion'] = want
c837807
                have = conf_dict['maxProtocolVersion']
c837807
                if CONST.TLS_VERSIONS[want][0] > CONST.TLS_VERSIONS[have][0]:
c837807
                    conf_dict['maxProtocolVersion'] = want
c837807
c837807
    return conf_dict
c837807
c837807
c837807
def get_ssl_cipher_config(json_data):
c837807
    conf_dict = dict.fromkeys(CONST.CONF_OPTIONS)
c837807
    for option in CONST.CONF_OPTIONS:
c837807
        conf_dict[option] = list()
c837807
    for section in CONST.CONF_SECTIONS:
c837807
        if section in json_data:
c837807
            for option in CONST.CONF_OPTIONS:
c837807
                if option in json_data[section]:
c837807
                    for value in json_data[section][option]:
c837807
                        if option == 'ciphers' and value.startswith('TLS_'):
c837807
                            if not 'ciphers_tls13' in conf_dict:
c837807
                                conf_dict['ciphers_tls13'] = list()
c837807
                            if not value in conf_dict['ciphers_tls13']:
c837807
                                conf_dict['ciphers_tls13'].append(value)
c837807
                        else:
c837807
                            if not value in conf_dict[option]:
c837807
                                conf_dict[option].append(value)
c837807
c837807
    return conf_dict
c837807
c837807
c837807
def print_config_file(conf_dict, sec_level):
c837807
    max_tls_proto = CONST.TLS_VERSIONS[conf_dict['maxProtocolVersion']][0]
c837807
    prelude = (
c837807
                  '# This application specific OpenSSL configuration enables all cipher',
c837807
                  '# algorithms, elliptic curves, and signature algorithms, which are',
c837807
                  '# needed for AusweisApp2 to provide full functionality to the end-user.',
c837807
                  '# The order of the algorithms in the list is of no importance, as the',
c837807
                  '# application chooses the algorithm used for a connection from a preset',
c837807
                  '# list, that is ordered in descending preference.  This configuration',
c837807
                  '# also limits the minimum and maximum cryptographic protocol versions',
c837807
                  '# to a range needed by AusweisApp2.  Additionally FIPS mode is enforced.',
c837807
                  '# The settings used to generate this file have been taken from the',
c837807
                  '# \'config.json\' file, which can be found in the same directory as this',
c837807
                  '# configuration file.',
c837807
                  '',
c837807
                  'openssl_conf = AusweisApp2_conf',
c837807
                  '',
c837807
                  '[AusweisApp2_conf]',
c837807
                  'ssl_conf = AusweisApp2_OpenSSL',
c837807
                  '',
c837807
                  '[AusweisApp2_OpenSSL]',
c837807
                  'alg_section = AusweisApp2_evp',
c837807
                  'system_default = AusweisApp2_ciphers',
c837807
                  '',
c837807
                  '[AusweisApp2_evp]',
c837807
                  'fips_mode = yes',
c837807
                  '',
c837807
                  '[AusweisApp2_ciphers]',
c837807
              )
c837807
    print('%s' % '\n'.join(prelude))
c837807
    print('MinProtocol = %s' % (CONST.TLS_VERSIONS[conf_dict['minProtocolVersion']][1]))
c837807
    print('MaxProtocol = %s' % (CONST.TLS_VERSIONS[conf_dict['maxProtocolVersion']][1]))
c837807
    if max_tls_proto >= CONST.TLS_VERSIONS['TlsV1_3'][0]:
c837807
        if 'ciphers_tls13' in conf_dict:
c837807
            print('Cipherlist = %s' % (':'.join(conf_dict['ciphers_tls13'])))
c837807
        else:
c837807
            print('Cipherlist = %s' % (':'.join(CONST.DEFAULT_CIPHERS_TLS13)))
c837807
    print('CipherString = @SECLEVEL=%d:%s' % (sec_level, ':'.join(conf_dict['ciphers'])))
c837807
    print('Curves = %s' % (':'.join(conf_dict['ellipticCurves'])))
c837807
    print('SignatureAlgorithms = %s' % (':'.join(conf_dict['signatureAlgorithms'])))
c837807
c837807
c837807
def main():
c837807
    if not len(sys.argv) == 2:
c837807
        sys.exit('Usage: %s <path_to_config.json>' % sys.argv[0])
c837807
c837807
    with open(sys.argv[1], 'r') as conf_file:
c837807
        conf = json.load(conf_file)
c837807
c837807
    ssl_conf = get_proto_ver(conf)
c837807
    ssl_conf.update(get_ssl_cipher_config(conf))
c837807
c837807
    print_config_file(ssl_conf, get_min_ssl_sec_level(conf))
c837807
c837807
c837807
if __name__ == '__main__':
c837807
    main()