#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Written for the OpenSSL appendix in the CrypTool book.
#
# (c) 2020, 2022 Bernd Busse / BE  [open source Apache 2 licence]
# Version: 1.0.1 (2020-05-26)
#          1.0.2 (2021-04-20)
#          1.0.3 (2022-03-22)
"""Pretty-print RSA private key from OpenSSL output

Pretty-print RSA private-key parameters from output by OpenSSL v1.1.1g.
- The output generated with `openssl pkey -in key.pem -noout -text > key.txt`
  can be pretty-printed with `python convert-pem-dec.py key.txt`.
- Remarks: option -text  is not enough;
           option -noout needed to have no base64 data at the beginning of key.txt
- The extraction is done in convert_pkey() by parsing the special structure of the textfile.
"""

import sys


class ParsingError(RuntimeError):
    """Generic Error while pretty-printing."""
    pass


def print_err(msg, *args, **kwargs):
    """Print formatted `msg` to `sys.stderr`."""
    print(msg.format(*args, **kwargs), file=sys.stderr)


def convert_pkey(pkey):
    """Parse private-key file `pkey`."""
    head = pkey.readline()
    if not head.startswith("RSA Private-Key: "):  # fixed eye-catcher string in openssl output !
        raise ParsingError("Can't find header. Is not in valid RSA private key format")

    privkey = {}

    # parse each input line
    line = pkey.readline().rstrip('\r\n')
    while line:
        # first line: COMPONENT:
        line = line.split(':')
        component = line[0]

        if len(line[1]) > 0:
            # value is inline: publicExponent already in base 10
            data = line[1].strip()
            privkey[component] = int(data.split(' ')[0])

            line = pkey.readline().rstrip('\r\n')
            continue

        # indented lines: big-endian hex
        data = []
        line = pkey.readline().rstrip('\r\n')
        while line.startswith("    "):
            data.append(line)
            line = pkey.readline().rstrip('\r\n')

        data = ''.join(data).replace(' ', '').replace(':', '')
        privkey[component] = int(data, 16)

    return privkey


def pretty_print_pkey(pkey):
    """Pretty-print private-key file `pkey`."""
    mapping = {
        "modulus": "n",
        "publicExponent": "e",
        "privateExponent": "d",
        "prime1": "p",
        "prime2": "q",
        "exponent1": "d_p",
        "exponent2": "d_q",
        "coefficient": "qinvp"
    }
    for param in mapping:
        if param not in pkey:
            raise ParsingError(f"Missing key parameter '{param}'")

    for param, symb in mapping.items():
        print(f"{param}:\n    {symb} = {pkey[param]:d}")


def main():
    """Handle interactive invocation."""
    if len(sys.argv) != 2:
        if len(sys.argv) < 2:
            print_err("Error: Missing parameter PKEY.txt")
        else:
            print_err(f"Error: Too many arguments: {sys.argv[1:]!s}")
        print_err("usage: python convert.py PKEY.txt")
        return False

    try:
        pkey_fname = sys.argv[1]
        with open(pkey_fname, "r") as pkey:
            try:
                privkey = convert_pkey(pkey)
                pretty_print_pkey(privkey)
            except ParsingError as err:
                print_err(f"Error: Pretty-print conversion failed: {err!s}")
                return False
    except OSError as err:
        print_err(f"Error: Cannot open PKEY: {err!s}")
        return False

    return True


if __name__ == '__main__':
    if not main():
        exit(1)

