#!/usr/bin/python3

#
# Copyright (C) 2019  Joe Testa <jtesta@positronsecurity.com>
#
# This tool will parse the list of TLS ciphersuites from IANA
# (https://www.iana.org/assignments/tls-parameters/tls-parameters.xml) into a C-struct
# for use in missing_ciphersuites.h.
#

import csv, sys
from datetime import date


# We must be given a path to a CSV file with the ciphersuites.  It can be obtained from
# <https://www.iana.org/assignments/tls-parameters/tls-parameters.xml>.
if len(sys.argv) != 2:
    print("\nUsage: %s tls_ciphers.csv\n\nHint: copy the TLS table in CSV format from <https://www.iana.org/assignments/tls-parameters/tls-parameters.xml>.\n" % sys.argv[0])
    exit()

csv_file = sys.argv[1]

print("/* Auto-generated by %s on %s. */" % (sys.argv[0], date.today().strftime("%B %d, %Y")))
print("struct missing_ciphersuite missing_ciphersuites[] = {")
with open(csv_file, 'r') as f:
    reader = csv.reader(f)
    for row in reader:
        id = row[0]
        cipher_name = row[1]

        # Skip the header.
        if '0x' not in id:
            continue

        # Skip reserved or unassigned ranges.  Also skip SCSV ciphers.
        if ('Reserved' in cipher_name) or ('Unassigned' in cipher_name) or ('TLS_FALLBACK_SCSV' in cipher_name) or ('TLS_EMPTY_RENEGOTIATION_INFO_SCSV' in cipher_name):
            continue

        # Convert '0xC0,0x87' to '0xC087'
        parsed_id = id[0:4] + id[7:9]
        if len(parsed_id) != 6:
            print("Error: parsed ID is not length 6: %s" % parsed_id)
            exit -1

        # Make an educated guess of the cipher's bit strength based on its name.
        bits = -1
        if 'AES_128' in cipher_name:
            bits = 128
        elif 'AES_256' in cipher_name:
            bits = 256
        elif 'CHACHA20' in cipher_name:
            bits = 256
        elif 'CAMELLIA_128' in cipher_name:
            bits = 128
        elif 'CAMELLIA_256' in cipher_name:
            bits = 256
        elif 'ARIA_128' in cipher_name:
            bits = 128
        elif 'ARIA_256' in cipher_name:
            bits = 256
        elif 'AEGIS_128' in cipher_name:
            bits = 128
        elif 'AEGIS_256' in cipher_name:
            bits = 256
        elif 'SEED' in cipher_name:
            bits = 128
        elif '3DES' in cipher_name:
            bits = 112
        elif 'DES40' in cipher_name:
            bits = 40
        elif '_DES_' in cipher_name:
            bits = 56
        elif 'RC4_128' in cipher_name:
            bits = 128
        elif 'RC4_40' in cipher_name:
            bits = 40
        elif 'IDEA' in cipher_name:
            bits = 128
        elif '_RC2_' in cipher_name:
            bits = 40
        elif 'GOSTR341112_256' in cipher_name:
            bits = 256
        elif '_SM4_' in cipher_name: # See http://www.gmbz.org.cn/upload/2018-04-04/1522788048733065051.pdf
            bits = 128

        print('  {%s, "%s", %d, VALL, 0},' % (parsed_id, cipher_name, bits))

    # These ciphers are reserved for private use.  Kind of like the 10.0.0.0/8 IPv4
    # addresses.
    print("\n  /* The ciphers below are reserved for private use (see RFC8446). */")
    for i in range(0, 256):
        low_byte = hex(i)[2:].upper()
        if len(low_byte) == 1:
            low_byte = '0' + low_byte

        parsed_id = '0xFF' + low_byte
        cipher_name = 'PRIVATE_CIPHER_%d' % i
        bits = -1
        print('  {%s, "%s", %d, VALL, 0},' % (parsed_id, cipher_name, bits))

print("};")
