#!/usr/bin/env python

from __future__ import print_function

"""
A somewhat complicated client for SignServer

Based on the simple sign.py SignServer client as a base class.
After obtaining the signature from the server, we wrap the information
in an IMA signature data structure and convert to a format useful for
consumption by the 'setfattr --restore' command.

The current --help output is as follows:

usage: ima-sign.py [--help] [-C CERTFILE] [-c CONFIG] [-d]
                       [-h {sha256,sha384,sha512,sha224,sha1}] [-o OFILE]
                       [-r TRIES] [-u URL] [-a] [--no-armor] [--force-v3-sigs]
                       [--no-force-v3-sigs] [-T FILESFROM]
                       [File [File ...]]

positional arguments:
  File                  File to be signed

optional arguments:
  --help                show this message
  -C CERTFILE           Certificate of public key
  -c CONFIG             Configuration file with defaults
  -d                    Add debugging output.
  -h {sha256,sha384,sha512,sha224,sha1}
                        Hashing algorithm to be used (default sha256).
  -o OFILE              Print sign output to ofile (default='.').
  -r TRIES              Number of times to retry server connections
  -u URL                Connect to the given "url" (host:port)
  -a, --armor           Create ASCII armored output
  --no-armor            Create binary OpenPGP format
  --force-v3-sigs       Use v4 signature format
  --no-force-v3-sigs    Use default v4 signature format
  -T FILESFROM, --files-from FILESFROM
                        get names to hash from FILE
"""

"""
RCSid:
	$Id: ima-sign.py,v 1.7 2023/10/04 19:48:17 sjg Exp $

	@(#) Copyright (c) 2015-2023 Simon J. Gerraty
	@(#) Copyright (c) 2014, Juniper Networks, Inc.

	This file is provided in the hope that it will
	be of use.  There is absolutely NO WARRANTY.
	Permission to copy, redistribute or otherwise
	use this file is hereby granted provided that
	the above copyright notice and this notice are
	left intact.

	This file uses sign.py as a base class.

	Please send copies of changes and bug-fixes to:
	sjg@crufty.net, ca@juniper.net
"""

import array
import base64
import binascii
import os
import pwd
import sys
import time

sys.dont_write_bytecode = True

import sign

pkey_hash_algo = { 'sha1':   sign.b('\x02'), 'sha256': sign.b('\x04'),
                   'sha384': sign.b('\x05'), 'sha512': sign.b('\x06'),
                   'sha224': sign.b('\x07') }

# This is a copy of code also found in openpgp-sign.py
def i2octets(n, i):
    """Convert an integer i into n octets"""
    v = bytearray()
    for pos in reversed(range(n)):
        v.append((i & (0xff << pos * 8)) >> pos * 8)
    return v

# This is a copy of code also found in pem.py
def pem_decode(text):
    """extract and decode pem data"""
    # pem = data between the BEGIN and END markers
    text = sign.s(text)
    begin = text.find('-----BEGIN')
    if begin >= 0:
        begin = text.find('\n', begin) + 1
        if begin > 0:
            pem = text[begin:]
            end = pem.find('-----END')
            if end > 0:
                pem = pem[:end]
                pem = pem.replace('\n', '')
            else:
                return None
        else:
            return None
    else:
        return None

    data = base64.b64decode(pem)
    return sign.s(data)

class IMASignClient(sign.SignClient):
    """Update the SignClient to generate OpenPGP signatures"""

    def __init__(self, conf={}):
        """save setup"""

        super(IMASignClient, self).__init__(conf)
        # override the base class
        self.hname = conf['hash']
        self.hid = pkey_hash_algo[self.hname]

    def sign(self, path, myhash, debug=0):
        """Sign the hash"""

        if self.debug > debug:
            debug = self.debug

        knobs, response = super(IMASignClient, self).sign(path, myhash, debug)

        # Convert the response to binary
        data = pem_decode(response)

        if 'IMAv2Keyid' in knobs:
            IMAv2Keyid = knobs['IMAv2Keyid']
        else:
            sign.error("This server is not configured for ima-sign operations")

        # EVM_IMA_XATTR_DIGSIG = 0x03, DIGSIG_VERSION_2 0x02
        hdr = sign.b('\x03\x02') + self.hid + binascii.unhexlify(IMAv2Keyid)
        hdr += i2octets(2, len(data)) + sign.b(data)
        response = '\n# file: ' + path + '\nsecurity.ima=0s'
        response += sign.s(base64.b64encode(hdr)) + '\n'

        return (knobs, response)

if __name__ == '__main__':
    conf = {}
    conf['sig_ext'] = '.sigima'
    try:
        sign.main(IMASignClient, None, conf)
    except SystemExit:
        raise
    except:
        # yes, this goes to stdout
        print("ERROR: {}".format(sys.exc_info()[1]))
        raise
    sys.exit(0)
