#!/usr/bin/env python

from __future__ import print_function


"""
RCSid:
	$Id: OpenSSLSigner.py,v 1.22 2024/07/05 19:45:51 sjg Exp $

	@(#) Copyright (c) 2012 Simon J. Gerraty

	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. 
      
	Please send copies of changes and bug-fixes to:
	sjg@crufty.net

"""

import sys
import os
import hashlib
from syslog import *
from Signer import *
from p3compat import *

# a bit of a faff, but gets us progdir as absolute path
# cf comes from Signer
progdir = cf.vars.Vars().vars['progdir']
for ht in cf.host_targets():
    sys.path.append('{}/{}'.format(progdir, ht))

if sys.version_info[0] == 2:
    try:
        import ossl2 as ossl
    except:
        import signer as ossl
else:
    import ossl3 as ossl
    
class OpenSSLSigner(Signer):
    """Interface to generic signing routine"""
    ossl = None
    
    def __init__(self, kfile, conf={}):
        self.pubkey = None
        if not kfile:
            kfile = conf.get('SigningKey')
        Signer.__init__(self, kfile, conf)
        self.debug = int(conf.get('debug', 0))
        self.ktype = conf.get('KeyType')
        self.hname = conf.get('Hash', 'sha256')
        self.pubkfile = conf.get('PublicKey')
        self.pubkey = None
        self.conf = conf
        if self.hname.startswith('fake'):
            import FakeHash
            self.hash = self.fake_hash
            self.hash_new = FakeHash.FakeHash
            if self.hname.endswith('N'):
                # we need to adapt
                self.hash_size = 0
            else:
                self.hash_size = 20
                if not self.hname.endswith('sha1'):
                    try:
                        self.hash_size = int(int(self.hname[-3:]) / 8)
                    except:
                        pass
                    if self.debug:
                        print('{}.size={}'.format(self.hname, self.hash_size))
            # pass "correct" name to self.ossl.sign_digest()
            self.hname = self.hname[4:]
        else:
            m = __import__('hashlib', fromlist=[self.hname])
            self.hash = getattr(m, self.hname)
            self.hash_size = self.hash().digest_size
        self.setup()

    def setup(self):
        if not self.ossl:
            self.ossl = ossl
        # any engine support should be setup before we load_key
        self.engine = self.conf.get('Engine')
        if self.engine:
            self.ossl.use_engine(self.engine)

        if self.kfile:
            self.load_key()
        if self.pubkfile:
            self.load_pubkey()

    def load_key(self):
        self.key = self.ossl.load_key(self.kfile)
        if not self.key:
            raise ValueError('cannot load key from {0}'.format(self.kfile))

    def load_pubkey(self):
        self.pubkey = self.ossl.load_pubkey(self.pubkfile)
        if not self.pubkey:
            raise ValueError('cannot load public key from {0}'.format(self.pubkfile))

    def fake_hash(self, msg=None):
        return self.hash_new(msg, digest_size=self.hash_size)

    def sign(self, msg):
        if not self.key:
            raise ValueError('need a key loaded')
        h = self.hash(b(msg))
        if self.debug:
            syslog(LOG_DEBUG, 'signing: {}'.format(h.hexdigest()))
        if self.hash_size == 0 and self.hname.endswith('N'):
            # we need to work it out
            n = len(h.digest())
            if n > 20:
                hname = '{}{}'.format(self.hname[0:-1], n * 8)
            elif n == 20:
                hname = 'sha1'
            elif n == 16:
                hname = 'md5'
            else:
                raise ValueError('unknown hash {} length {}'.format(self.hname, n))
            if self.debug:
                syslog(LOG_DEBUG, 'hname={}'.format(hname))
        else:
            hname = self.hname
        return self.ossl.sign_digest(self.key, b(hname), h.digest())

    def verify(self, mdata, sig):
        """verify sig against mdata"""
        h = self.hash(b(mdata))
        if self.debug:
            syslog(LOG_DEBUG, 'verifying: {}'.format(h.hexdigest()))
        return self.ossl.verify_digest(self.pubkey, self.hname, h.digest(), sig)


if __name__ == '__main__':
    import getopt, sys

    debug = 0
    sfile = '/tmp/the.osig'
    mfile = '/tmp/the'

    conf = {'Hash': 'fakesha256'}
    opts,args = getopt.getopt(sys.argv[1:], 'c:dk:m:s:h:p:')
    for o,a in opts:
        if o == '-c':
            conf = cf.loadConfig(a, conf)
        elif o == '-d':
            debug += 1
        elif o == '-h':
            conf['Hash'] = a
        elif o == '-k':
            conf['SigningKey'] = a
        elif o == '-p':
            conf['PublicKey'] = a
        elif o == '-m':
            mfile = a
        elif o == '-s':
            sfile = a

    conf['debug'] = debug
    if 'PublicKey' not in conf and 'Certs' in conf:
        conf['PublicKey'] = conf['Certs']
    signer = OpenSSLSigner(None, conf)
    msg = open(mfile, 'rb').read()
    hname = conf.get('Hash', 'sha256')
    if hname.startswith('fake'):
        hname = hname[4:]
    m = __import__('hashlib', fromlist=[hname])
    hash = getattr(m, hname)
    h = hash(msg)
    sig = signer.sign(h.hexdigest())
    try:
        signer.load_pubkey()
        if signer.verify(h.hexdigest(), sig):
            print("Verified")
        else:
            print("Not verified")
    except:
        print("Verify failed")
        pass
    print(pem_encode('SIGNATURE', sig))

