# SPDX-License-Identifier: GPL-2.0+
# Copyright 2022 Google LLC
#
"""Bintool implementation for openssl

openssl provides a number of features useful for signing images

Documentation is at https://www.coreboot.org/CBFS

Source code is at https://www.openssl.org/
"""

import hashlib

from binman import bintool
from u_boot_pylib import tools


VALID_SHAS = [256, 384, 512, 224]
SHA_OIDS = {256:'2.16.840.1.101.3.4.2.1',
            384:'2.16.840.1.101.3.4.2.2',
            512:'2.16.840.1.101.3.4.2.3',
            224:'2.16.840.1.101.3.4.2.4'}

class Bintoolopenssl(bintool.Bintool):
    """openssl tool

    This bintool supports creating new openssl certificates.

    It also supports fetching a binary openssl

    Documentation about openssl is at https://www.openssl.org/
    """
    def __init__(self, name):
        super().__init__(
            name, 'openssl cryptography toolkit',
            version_regex=r'OpenSSL (.*) \(', version_args='version')

    def x509_cert(self, cert_fname, input_fname, key_fname, cn, revision,
                  config_fname):
        """Create a certificate

        Args:
            cert_fname (str): Filename of certificate to create
            input_fname (str): Filename containing data to sign
            key_fname (str): Filename of .pem file
            cn (str): Common name
            revision (int): Revision number
            config_fname (str): Filename to write fconfig into

        Returns:
            str: Tool output
        """
        indata = tools.read_file(input_fname)
        hashval = hashlib.sha512(indata).hexdigest()
        with open(config_fname, 'w', encoding='utf-8') as outf:
            print(f'''[ req ]
distinguished_name     = req_distinguished_name
x509_extensions        = v3_ca
prompt                 = no
dirstring_type         = nobmp

[ req_distinguished_name ]
CN                     = {cert_fname}

[ v3_ca ]
basicConstraints       = CA:true
1.3.6.1.4.1.294.1.3    = ASN1:SEQUENCE:swrv
1.3.6.1.4.1.294.1.34   = ASN1:SEQUENCE:sysfw_image_integrity

[ swrv ]
swrv = INTEGER:{revision}

[ sysfw_image_integrity ]
shaType                = OID:2.16.840.1.101.3.4.2.3
shaValue               = FORMAT:HEX,OCT:{hashval}
imageSize              = INTEGER:{len(indata)}
''', file=outf)
        args = ['req', '-new', '-x509', '-key', key_fname, '-nodes',
                '-outform', 'DER', '-out', cert_fname, '-config', config_fname,
                '-sha512']
        return self.run_cmd(*args)

    def x509_cert_sysfw(self, cert_fname, input_fname, key_fname, sw_rev,
                  config_fname, req_dist_name_dict):
        """Create a certificate to be booted by system firmware

        Args:
            cert_fname (str): Filename of certificate to create
            input_fname (str): Filename containing data to sign
            key_fname (str): Filename of .pem file
            sw_rev (int): Software revision
            config_fname (str): Filename to write fconfig into
            req_dist_name_dict (dict): Dictionary containing key-value pairs of
            req_distinguished_name section extensions, must contain extensions for
            C, ST, L, O, OU, CN and emailAddress

        Returns:
            str: Tool output
        """
        indata = tools.read_file(input_fname)
        hashval = hashlib.sha512(indata).hexdigest()
        with open(config_fname, 'w', encoding='utf-8') as outf:
            print(f'''[ req ]
distinguished_name     = req_distinguished_name
x509_extensions        = v3_ca
prompt                 = no
dirstring_type         = nobmp

[ req_distinguished_name ]
C                      = {req_dist_name_dict['C']}
ST                     = {req_dist_name_dict['ST']}
L                      = {req_dist_name_dict['L']}
O                      = {req_dist_name_dict['O']}
OU                     = {req_dist_name_dict['OU']}
CN                     = {req_dist_name_dict['CN']}
emailAddress           = {req_dist_name_dict['emailAddress']}

[ v3_ca ]
basicConstraints       = CA:true
1.3.6.1.4.1.294.1.3    = ASN1:SEQUENCE:swrv
1.3.6.1.4.1.294.1.34   = ASN1:SEQUENCE:sysfw_image_integrity
1.3.6.1.4.1.294.1.35   = ASN1:SEQUENCE:sysfw_image_load

[ swrv ]
swrv = INTEGER:{sw_rev}

[ sysfw_image_integrity ]
shaType                = OID:2.16.840.1.101.3.4.2.3
shaValue               = FORMAT:HEX,OCT:{hashval}
imageSize              = INTEGER:{len(indata)}

[ sysfw_image_load ]
destAddr = FORMAT:HEX,OCT:00000000
authInPlace = INTEGER:2
''', file=outf)
        args = ['req', '-new', '-x509', '-key', key_fname, '-nodes',
                '-outform', 'DER', '-out', cert_fname, '-config', config_fname,
                '-sha512']
        return self.run_cmd(*args)

    def x509_cert_rom(self, cert_fname, input_fname, key_fname, sw_rev,
                  config_fname, req_dist_name_dict, cert_type, bootcore,
                  bootcore_opts, load_addr, sha):
        """Create a certificate

        Args:
            cert_fname (str): Filename of certificate to create
            input_fname (str): Filename containing data to sign
            key_fname (str): Filename of .pem file
            sw_rev (int): Software revision
            config_fname (str): Filename to write fconfig into
            req_dist_name_dict (dict): Dictionary containing key-value pairs of
            req_distinguished_name section extensions, must contain extensions for
            C, ST, L, O, OU, CN and emailAddress
            cert_type (int): Certification type
            bootcore (int): Booting core
            load_addr (int): Load address of image
            sha (int): Hash function

        Returns:
            str: Tool output
        """
        indata = tools.read_file(input_fname)
        hashval = hashlib.sha512(indata).hexdigest()
        with open(config_fname, 'w', encoding='utf-8') as outf:
            print(f'''
[ req ]
 distinguished_name     = req_distinguished_name
 x509_extensions        = v3_ca
 prompt                 = no
 dirstring_type         = nobmp

 [ req_distinguished_name ]
C                      = {req_dist_name_dict['C']}
ST                     = {req_dist_name_dict['ST']}
L                      = {req_dist_name_dict['L']}
O                      = {req_dist_name_dict['O']}
OU                     = {req_dist_name_dict['OU']}
CN                     = {req_dist_name_dict['CN']}
emailAddress           = {req_dist_name_dict['emailAddress']}

 [ v3_ca ]
 basicConstraints = CA:true
 1.3.6.1.4.1.294.1.1 = ASN1:SEQUENCE:boot_seq
 1.3.6.1.4.1.294.1.2 = ASN1:SEQUENCE:image_integrity
 1.3.6.1.4.1.294.1.3 = ASN1:SEQUENCE:swrv
# 1.3.6.1.4.1.294.1.4 = ASN1:SEQUENCE:encryption
 1.3.6.1.4.1.294.1.8 = ASN1:SEQUENCE:debug

 [ boot_seq ]
 certType = INTEGER:{cert_type}
 bootCore = INTEGER:{bootcore}
 bootCoreOpts = INTEGER:{bootcore_opts}
 destAddr = FORMAT:HEX,OCT:{load_addr:08x}
 imageSize = INTEGER:{len(indata)}

 [ image_integrity ]
 shaType = OID:{SHA_OIDS[sha]}
 shaValue = FORMAT:HEX,OCT:{hashval}

 [ swrv ]
 swrv = INTEGER:{sw_rev}

# [ encryption ]
# initalVector = FORMAT:HEX,OCT:TEST_IMAGE_ENC_IV
# randomString = FORMAT:HEX,OCT:TEST_IMAGE_ENC_RS
# iterationCnt = INTEGER:TEST_IMAGE_KEY_DERIVE_INDEX
# salt = FORMAT:HEX,OCT:TEST_IMAGE_KEY_DERIVE_SALT

 [ debug ]
 debugUID = FORMAT:HEX,OCT:0000000000000000000000000000000000000000000000000000000000000000
 debugType = INTEGER:4
 coreDbgEn = INTEGER:0
 coreDbgSecEn = INTEGER:0
''', file=outf)
        args = ['req', '-new', '-x509', '-key', key_fname, '-nodes',
                '-outform', 'DER', '-out', cert_fname, '-config', config_fname,
                '-sha512']
        return self.run_cmd(*args)

    def x509_cert_rom_combined(self, cert_fname, input_fname, key_fname, sw_rev,
                  config_fname, req_dist_name_dict, load_addr, sha, total_size, num_comps,
                  sysfw_inner_cert_ext_boot_sequence_string, dm_data_ext_boot_sequence_string,
                  imagesize_sbl, hashval_sbl, load_addr_sysfw, imagesize_sysfw,
                  hashval_sysfw, load_addr_sysfw_data, imagesize_sysfw_data,
                  hashval_sysfw_data, sysfw_inner_cert_ext_boot_block,
                  dm_data_ext_boot_block):
        """Create a certificate

        Args:
            cert_fname (str): Filename of certificate to create
            input_fname (str): Filename containing data to sign
            key_fname (str): Filename of .pem file
            sw_rev (int): Software revision
            config_fname (str): Filename to write fconfig into
            req_dist_name_dict (dict): Dictionary containing key-value pairs of
            req_distinguished_name section extensions, must contain extensions for
            C, ST, L, O, OU, CN and emailAddress
            cert_type (int): Certification type
            bootcore (int): Booting core
            load_addr (int): Load address of image
            sha (int): Hash function

        Returns:
            str: Tool output
        """
        indata = tools.read_file(input_fname)
        hashval = hashlib.sha512(indata).hexdigest()
        sha_type = SHA_OIDS[sha]
        with open(config_fname, 'w', encoding='utf-8') as outf:
            print(f'''
[ req ]
distinguished_name     = req_distinguished_name
x509_extensions        = v3_ca
prompt                 = no
dirstring_type         = nobmp

[ req_distinguished_name ]
C                      = {req_dist_name_dict['C']}
ST                     = {req_dist_name_dict['ST']}
L                      = {req_dist_name_dict['L']}
O                      = {req_dist_name_dict['O']}
OU                     = {req_dist_name_dict['OU']}
CN                     = {req_dist_name_dict['CN']}
emailAddress           = {req_dist_name_dict['emailAddress']}

[ v3_ca ]
basicConstraints = CA:true
1.3.6.1.4.1.294.1.3=ASN1:SEQUENCE:swrv
1.3.6.1.4.1.294.1.9=ASN1:SEQUENCE:ext_boot_info

[swrv]
swrv=INTEGER:{sw_rev}

[ext_boot_info]
extImgSize=INTEGER:{total_size}
numComp=INTEGER:{num_comps}
sbl=SEQUENCE:sbl
sysfw=SEQUENCE:sysfw
sysfw_data=SEQUENCE:sysfw_data
{sysfw_inner_cert_ext_boot_sequence_string}
{dm_data_ext_boot_sequence_string}

[sbl]
compType = INTEGER:1
bootCore = INTEGER:16
compOpts = INTEGER:0
destAddr = FORMAT:HEX,OCT:{load_addr:08x}
compSize = INTEGER:{imagesize_sbl}
shaType  = OID:{sha_type}
shaValue = FORMAT:HEX,OCT:{hashval_sbl}

[sysfw]
compType = INTEGER:2
bootCore = INTEGER:0
compOpts = INTEGER:0
destAddr = FORMAT:HEX,OCT:{load_addr_sysfw:08x}
compSize = INTEGER:{imagesize_sysfw}
shaType  = OID:{sha_type}
shaValue = FORMAT:HEX,OCT:{hashval_sysfw}

[sysfw_data]
compType = INTEGER:18
bootCore = INTEGER:0
compOpts = INTEGER:0
destAddr = FORMAT:HEX,OCT:{load_addr_sysfw_data:08x}
compSize = INTEGER:{imagesize_sysfw_data}
shaType  = OID:{sha_type}
shaValue = FORMAT:HEX,OCT:{hashval_sysfw_data}

{sysfw_inner_cert_ext_boot_block}

{dm_data_ext_boot_block}
        ''', file=outf)
        args = ['req', '-new', '-x509', '-key', key_fname, '-nodes',
                '-outform', 'DER', '-out', cert_fname, '-config', config_fname,
                '-sha512']
        return self.run_cmd(*args)

    def fetch(self, method):
        """Fetch handler for openssl

        This installs the openssl package using the apt utility.

        Args:
            method (FETCH_...): Method to use

        Returns:
            True if the file was fetched and now installed, None if a method
            other than FETCH_BIN was requested

        Raises:
            Valuerror: Fetching could not be completed
        """
        if method != bintool.FETCH_BIN:
            return None
        return self.apt_install('openssl')