diff options
author | lloyd <[email protected]> | 2014-01-01 21:20:55 +0000 |
---|---|---|
committer | lloyd <[email protected]> | 2014-01-01 21:20:55 +0000 |
commit | 197dc467dec28a04c3b2f30da7cef122dfbb13e9 (patch) | |
tree | cdbd3ddaec051c72f0a757db461973d90c37b97a /src/scripts | |
parent | 62faac373c07cfe10bc8c309e89ebdd30d8e5eaa (diff) |
Shuffle things around. Add NIST X.509 test to build.
Diffstat (limited to 'src/scripts')
-rwxr-xr-x | src/scripts/dist.py | 362 | ||||
-rwxr-xr-x | src/scripts/tls_suite_info.py | 273 |
2 files changed, 635 insertions, 0 deletions
diff --git a/src/scripts/dist.py b/src/scripts/dist.py new file mode 100755 index 000000000..56aabd45f --- /dev/null +++ b/src/scripts/dist.py @@ -0,0 +1,362 @@ +#!/usr/bin/env python + +""" +Release script for botan (http://botan.randombit.net/) + +(C) 2011, 2012, 2013 Jack Lloyd + +Distributed under the terms of the Botan license +""" + +import errno +import logging +import optparse +import os +import shlex +import StringIO +import shutil +import subprocess +import sys +import tarfile +import datetime +import hashlib + +def check_subprocess_results(subproc, name): + (stdout, stderr) = subproc.communicate() + + stdout = stdout.strip() + stderr = stderr.strip() + + if subproc.returncode != 0: + if stdout != '': + logging.error(stdout) + if stderr != '': + logging.error(stderr) + raise Exception('Running %s failed' % (name)) + else: + if stderr != '': + logging.debug(stderr) + + return stdout + +def run_monotone(db, args): + cmd = ['mtn', '--db', db] + args + + logging.debug('Running %s' % (' '.join(cmd))) + + mtn = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + return check_subprocess_results(mtn, 'mtn') + +def get_certs(db, rev_id): + tokens = shlex.split(run_monotone(db, ['automate', 'certs', rev_id])) + + def usable_cert(cert): + if 'signature' not in cert or cert['signature'] != 'ok': + return False + if 'trust' not in cert or cert['trust'] != 'trusted': + return False + if 'name' not in cert or 'value' not in cert: + return False + return True + + def cert_builder(tokens): + pairs = zip(tokens[::2], tokens[1::2]) + current_cert = {} + for pair in pairs: + if pair[0] == 'trust': + if usable_cert(current_cert): + name = current_cert['name'] + value = current_cert['value'] + current_cert = {} + + logging.debug('Cert %s "%s" for rev %s' % (name, value, rev_id)) + yield (name, value) + + current_cert[pair[0]] = pair[1] + + certs = dict(cert_builder(tokens)) + return certs + +def datestamp(db, rev_id): + certs = get_certs(db, rev_id) + + if 'date' in certs: + datestamp = int(certs['date'].replace('-','')[0:8]) + logging.info('Using datestamp %s for rev %s' % (datestamp, rev_id)) + return datestamp + + logging.info('Could not retreive date for %s' % (rev_id)) + return 0 + +def gpg_sign(keyid, passphrase_file, files, detached = True): + + options = ['--armor', '--detach-sign'] if detached else ['--clearsign'] + + gpg_cmd = ['gpg', '--batch'] + options + ['--local-user', keyid] + if passphrase_file != None: + gpg_cmd[1:1] = ['--passphrase-file', passphrase_file] + + for filename in files: + logging.info('Signing %s using PGP id %s' % (filename, keyid)) + + cmd = gpg_cmd + [filename] + + logging.debug('Running %s' % (' '.join(cmd))) + + gpg = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + check_subprocess_results(gpg, 'gpg') + + return [filename + '.asc' for filename in files] + +def parse_args(args): + parser = optparse.OptionParser( + "usage: %prog [options] <version #>\n" + + " %prog [options] snapshot <branch>" + ) + + parser.add_option('--verbose', action='store_true', + default=False, help='Extra debug output') + + parser.add_option('--quiet', action='store_true', + default=False, help='Only show errors') + + parser.add_option('--output-dir', metavar='DIR', + default='.', + help='Where to place output (default %default)') + + parser.add_option('--mtn-db', metavar='DB', + default=os.getenv('BOTAN_MTN_DB', ''), + help='Set monotone db (default \'%default\')') + + parser.add_option('--print-output-names', action='store_true', + help='Print output archive filenames to stdout') + + parser.add_option('--archive-types', metavar='LIST', default='tbz,tgz', + help='Set archive types to generate (default %default)') + + parser.add_option('--pgp-key-id', metavar='KEYID', + default='EFBADFBC', + help='PGP signing key (default %default, "none" to disable)') + + parser.add_option('--pgp-passphrase-file', metavar='FILE', + default=None, + help='PGP signing key passphrase file') + + parser.add_option('--write-hash-file', metavar='FILE', default=None, + help='Write a file with checksums') + + return parser.parse_args(args) + +def remove_file_if_exists(fspath): + try: + os.unlink(fspath) + except OSError as e: + if e.errno != errno.ENOENT: + raise + +def main(args = None): + if args is None: + args = sys.argv[1:] + + (options, args) = parse_args(args) + + def log_level(): + if options.verbose: + return logging.DEBUG + if options.quiet: + return logging.ERROR + return logging.INFO + + logging.basicConfig(stream = sys.stderr, + format = '%(levelname) 7s: %(message)s', + level = log_level()) + + if options.mtn_db == '': + logging.error('No monotone db set (use --mtn-db)') + return 1 + + if not os.access(options.mtn_db, os.R_OK): + logging.error('Monotone db %s not found' % (options.mtn_db)) + return 1 + + if len(args) == 0 or len(args) >= 3: + logging.error('Usage error, try --help') + return 1 + + # Sanity check arguments + + if args[0] == 'snapshot': + + if len(args) == 1: + logging.error('Missing branch name for snapshot command') + return 1 + + logging.info('Creating snapshot release from branch %s', args[1]) + + elif len(args) == 1: + try: + logging.info('Creating release for version %s' % (args[0])) + + (major,minor,patch) = map(int, args[0].split('.')) + + assert args[0] == '%d.%d.%d' % (major,minor,patch) + except: + logging.error('Invalid version number %s' % (args[0])) + return 1 + + else: + logging.error('Usage error, try --help') + return 1 + + def selector(args): + if args[0] == 'snapshot': + return 'h:' + args[1] + else: + return 't:' + args[0] + + def output_name(args): + if args[0] == 'snapshot': + datestamp = datetime.date.today().isoformat().replace('-', '') + + def snapshot_name(branch): + if branch == 'net.randombit.botan': + return 'trunk' + elif branch == 'net.randombit.botan.1_10': + return '1.10' + else: + return branch + + return 'botan-%s-snapshot-%s' % (snapshot_name(args[1]), datestamp) + else: + return 'Botan-' + args[0] + + rev_id = run_monotone(options.mtn_db, ['automate', 'select', selector(args)]) + + if rev_id == '': + logging.error('No revision matching %s found' % (selector(args))) + return 2 + + logging.info('Found revision id %s' % (rev_id)) + + output_basename = output_name(args) + + logging.debug('Output basename %s' % (output_basename)) + + if os.access(output_basename, os.X_OK): + logging.info('Removing existing output dir %s' % (output_basename)) + shutil.rmtree(output_basename) + + run_monotone(options.mtn_db, + ['checkout', '--quiet', '-r', rev_id, output_basename]) + + shutil.rmtree(os.path.join(output_basename, '_MTN')) + remove_file_if_exists(os.path.join(output_basename, '.mtn-ignore')) + + version_file = os.path.join(output_basename, 'botan_version.py') + + if os.access(version_file, os.R_OK): + # rewrite botan_version.py + + contents = open(version_file).readlines() + + def content_rewriter(): + for line in contents: + if line == 'release_vc_rev = None\n': + yield 'release_vc_rev = \'mtn:%s\'\n' % (rev_id) + elif line == 'release_datestamp = 0\n': + yield 'release_datestamp = %d\n' % (datestamp(options.mtn_db, rev_id)) + elif line == "release_type = \'unreleased\'\n": + if args[0] == 'snapshot': + yield "release_type = 'snapshot'\n" + else: + yield "release_type = 'released'\n" + else: + yield line + + open(version_file, 'w').write(''.join(list(content_rewriter()))) + else: + logging.error('Cannot find %s' % (version_file)) + return 2 + + try: + os.makedirs(options.output_dir) + except OSError as e: + if e.errno != errno.EEXIST: + logging.error('Creating dir %s failed %s' % (options.output_dir, e)) + return 2 + + output_files = [] + + archives = options.archive_types.split(',') if options.archive_types != '' else [] + + hash_file = None + if options.write_hash_file != None: + hash_file = open(options.write_hash_file, 'w') + + for archive in archives: + logging.debug('Writing archive type "%s"' % (archive)) + + output_archive = output_basename + '.' + archive + + remove_file_if_exists(output_archive) + remove_file_if_exists(output_archive + '.asc') + + if archive in ['tgz', 'tbz']: + + def write_mode(): + if archive == 'tgz': + return 'w:gz' + elif archive == 'tbz': + return 'w:bz2' + + archive = tarfile.open(output_archive, write_mode()) + archive.add(output_basename) + archive.close() + + if hash_file != None: + sha256 = hashlib.new('sha256') + sha256.update(open(output_archive).read()) + hash_file.write("%s %s\n" % (sha256.hexdigest(), output_archive)) + else: + raise Exception('Unknown archive type "%s"' % (archive)) + + output_files.append(output_archive) + + if hash_file != None: + hash_file.close() + + shutil.rmtree(output_basename) + + if options.print_output_names: + for output_file in output_files: + print(output_file) + + if options.pgp_key_id != 'none': + if options.write_hash_file != None: + output_files += gpg_sign(options.pgp_key_id, options.pgp_passphrase_file, + [options.write_hash_file], False) + else: + output_files += gpg_sign(options.pgp_key_id, options.pgp_passphrase_file, + output_files, True) + + if options.output_dir != '.': + for output_file in output_files: + logging.debug('Moving %s to %s' % (output_file, options.output_dir)) + shutil.move(output_file, os.path.join(options.output_dir, output_file)) + + return 0 + +if __name__ == '__main__': + try: + sys.exit(main()) + except Exception as e: + logging.error(e) + import traceback + logging.info(traceback.format_exc()) + sys.exit(1) diff --git a/src/scripts/tls_suite_info.py b/src/scripts/tls_suite_info.py new file mode 100755 index 000000000..dd507bc28 --- /dev/null +++ b/src/scripts/tls_suite_info.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python2 + +""" +Used to generate src/tls/tls_suite_info.cpp + +(C) 2011, 2012, 2013 Jack Lloyd + +Distributed under the terms of the Botan license + +""" + +import sys +import re +import datetime +import hashlib + +def to_ciphersuite_info(code, name): + + (sig_and_kex,cipher_and_mac) = name.split('_WITH_') + + if sig_and_kex == 'RSA': + sig_algo = 'RSA' + kex_algo = 'RSA' + elif 'PSK' in sig_and_kex: + sig_algo = '' + kex_algo = sig_and_kex + elif 'SRP' in sig_and_kex: + srp_info = sig_and_kex.split('_') + if len(srp_info) == 2: # 'SRP_' + hash + kex_algo = sig_and_kex + sig_algo = '' + else: + kex_algo = '_'.join(srp_info[0:-1]) + sig_algo = srp_info[-1] + else: + (kex_algo, sig_algo) = sig_and_kex.split('_') + + cipher_and_mac = cipher_and_mac.split('_') + + mac_algo = cipher_and_mac[-1] + + cipher = cipher_and_mac[:-1] + + if mac_algo == '8' and cipher[-1] == 'CCM': + cipher = cipher[:-1] + mac_algo = 'CCM_8' + + if mac_algo == 'CCM': + cipher += ['CCM'] + mac_algo = 'SHA256' + elif mac_algo == 'CCM_8': + cipher += ['CCM-8'] + mac_algo = 'SHA256' + + cipher_info = { + 'RC4': ('RC4',None), + 'IDEA': ('IDEA',16), + 'DES': ('DES',8), + '3DES': ('3DES',24), + 'CAMELLIA': ('Camellia',None), + 'AES': ('AES',None), + 'SEED': ('SEED',16), + 'ARIA': ('ARIA',16) + } + + tls_to_botan_names = { + 'anon': '', + 'MD5': 'MD5', + 'SHA': 'SHA-1', + 'SHA256': 'SHA-256', + 'SHA384': 'SHA-384', + 'SHA512': 'SHA-512', + + 'RC4': 'RC4', + '3DES': 'TripleDES', + + 'DSS': 'DSA', + 'ECDSA': 'ECDSA', + 'RSA': 'RSA', + 'SRP_SHA': 'SRP_SHA', + 'DHE': 'DH', + 'DH': 'DH', + 'ECDHE': 'ECDH', + 'ECDH': 'ECDH', + '': '', + 'PSK': 'PSK', + 'DHE_PSK': 'DHE_PSK', + 'PSK_DHE': 'DHE_PSK', + 'ECDHE_PSK': 'ECDHE_PSK', + } + + mac_keylen = { + 'MD5': 16, + 'SHA-1': 20, + 'SHA-256': 32, + 'SHA-384': 48, + 'SHA-512': 64, + } + + mac_algo = tls_to_botan_names[mac_algo] + sig_algo = tls_to_botan_names[sig_algo] + kex_algo = tls_to_botan_names[kex_algo] + + (cipher_algo, cipher_keylen) = cipher_info[cipher[0]] + + if cipher_keylen is None: + cipher_keylen = int(cipher[1]) / 8 + + if cipher_algo in ['AES', 'Camellia']: + cipher_algo += '-%d' % (cipher_keylen*8) + + modestr = '' + mode = '' + ivlen = 0 + if cipher_algo != 'RC4': + mode = cipher[-1] + if mode not in ['CBC', 'GCM', 'CCM-8', 'CCM', 'OCB']: + print "#warning Unknown mode %s" % (' '.join(cipher)) + + ivlen = 8 if cipher_algo == '3DES' else 16 + + if mode != 'CBC': + cipher_algo += '/' + mode + + if cipher_algo != 'RC4' and mode != 'CBC': + return 'Ciphersuite(0x%s, "%s", "%s", "%s", %d, %d, "AEAD", %d, "%s")' % ( + code, sig_algo, kex_algo, cipher_algo, cipher_keylen, 4, 0, mac_algo) + else: + return 'Ciphersuite(0x%s, "%s", "%s", "%s", %d, %d, "%s", %d)' % ( + code, sig_algo, kex_algo, cipher_algo, cipher_keylen, ivlen, mac_algo, mac_keylen[mac_algo]) + +def open_input(args): + iana_url = 'https://www.iana.org/assignments/tls-parameters/tls-parameters.txt' + + if len(args) == 1: + try: + return open('tls-parameters.txt') + except: + pass + + import urllib2 + return urllib2.urlopen(iana_url) + else: + return open(args[1]) + +def main(args = None): + if args is None: + args = sys.argv + + weak_crypto = ['EXPORT', 'RC2', 'IDEA', '_DES_', 'WITH_NULL'] + static_dh = ['ECDH_ECDSA', 'ECDH_RSA', 'DH_DSS', 'DH_RSA'] # not supported + protocol_goop = ['SCSV', 'KRB5'] + maybe_someday = ['ARIA', 'RSA_PSK'] + not_supported = weak_crypto + static_dh + protocol_goop + maybe_someday + + include_srp_aead = False + include_ocb = False + include_eax = False + save_file = True + + ciphersuite_re = re.compile(' +0x([0-9a-fA-F][0-9a-fA-F]),0x([0-9a-fA-F][0-9a-fA-F]) + TLS_([A-Za-z_0-9]+) ') + + suites = {} + suite_codes = {} + + contents = '' + + for line in open_input(args): + contents += line + match = ciphersuite_re.match(line) + if match: + code = match.group(1) + match.group(2) + name = match.group(3) + + should_use = True + for ns in not_supported: + if ns in name: + should_use = False + + if should_use: + suites[name] = (code, to_ciphersuite_info(code, name)) + + sha1 = hashlib.sha1() + sha1.update(contents) + contents_hash = sha1.hexdigest() + + if save_file: + out = open('tls-parameters.txt', 'w') + out.write(contents) + out.close() + + def define_custom_ciphersuite(name, code): + suites[name] = (code, to_ciphersuite_info(code, name)) + + # From http://tools.ietf.org/html/draft-ietf-tls-56-bit-ciphersuites-01 + define_custom_ciphersuite('DHE_DSS_WITH_RC4_128_SHA', '0066') + + # Expermental things + if include_ocb: + define_custom_ciphersuite('ECDHE_ECDSA_AES_128_OCB_SHA256', 'FF80') + define_custom_ciphersuite('ECDHE_ECDSA_WITH_AES_256_OCB_SHA384', 'FF81') + define_custom_ciphersuite('ECDHE_RSA_WITH_AES_128_OCB_SHA256', 'FF82') + define_custom_ciphersuite('ECDHE_RSA_WITH_AES_256_OCB_SHA384', 'FF83') + + define_custom_ciphersuite('ECDHE_PSK_WITH_AES_128_OCB_SHA256', 'FF85') + define_custom_ciphersuite('ECDHE_PSK_WITH_AES_256_OCB_SHA384', 'FF86') + + if include_eax: + define_custom_ciphersuite('ECDHE_ECDSA_WITH_AES_128_EAX_SHA256', 'FF90') + define_custom_ciphersuite('ECDHE_ECDSA_WITH_AES_256_EAX_SHA384', 'FF91') + define_custom_ciphersuite('ECDHE_RSA_WITH_AES_128_EAX_SHA256', 'FF92') + define_custom_ciphersuite('ECDHE_RSA_WITH_AES_256_EAX_SHA384', 'FF93') + + if include_srp_aead: + define_custom_ciphersuite('SRP_SHA_WITH_AES_256_GCM_SHA384', 'FFA0') + define_custom_ciphersuite('SRP_SHA_RSA_WITH_AES_256_GCM_SHA384', 'FFA1') + define_custom_ciphersuite('SRP_SHA_DSS_WITH_AES_256_GCM_SHA384', 'FFA2') + define_custom_ciphersuite('SRP_SHA_ECDSA_WITH_AES_256_GCM_SHA384', 'FFA3') + + if include_ocb: + define_custom_ciphersuite('SRP_SHA_WITH_AES_256_OCB_SHA384', 'FFA4') + define_custom_ciphersuite('SRP_SHA_RSA_WITH_AES_256_OCB_SHA384', 'FFA5') + define_custom_ciphersuite('SRP_SHA_DSS_WITH_AES_256_OCB_SHA384', 'FFA6') + define_custom_ciphersuite('SRP_SHA_ECDSA_WITH_AES_256_OCB_SHA384', 'FFA7') + + if include_eax: + define_custom_ciphersuite('SRP_SHA_WITH_AES_256_EAX_SHA384', 'FFA8') + define_custom_ciphersuite('SRP_SHA_RSA_WITH_AES_256_EAX_SHA384', 'FFA9') + define_custom_ciphersuite('SRP_SHA_DSS_WITH_AES_256_EAX_SHA384', 'FFAA') + define_custom_ciphersuite('SRP_SHA_ECDSA_WITH_AES_256_EAX_SHA384', 'FFAB') + + + def header(): + return """/* +* TLS cipher suite information +* +* This file was automatically generated from the IANA assignments +* (tls-parameters.txt hash %s) +* by %s on %s +* +* Released under the terms of the Botan license +*/ +""" % (contents_hash, sys.argv[0], datetime.date.today().strftime("%Y-%m-%d")) + + print header() + + print """#include <botan/tls_ciphersuite.h> + +namespace Botan { + +namespace TLS { + +Ciphersuite Ciphersuite::by_id(u16bit suite) + { + switch(suite) + {""" + + for k in sorted(suites.keys()): + print " case 0x%s: // %s" % (suites[k][0], k) + print " return %s;" % (suites[k][1]) + print + + print """ } + + return Ciphersuite(); // some unknown ciphersuite + } + +} + +}""" + +if __name__ == '__main__': + sys.exit(main()) |