#! /usr/bin/python3 -s
#
# Repeatable ECDSA key generator using the system authseed
# this uses python-ecdsa but monkey patches os.urandom to
# an iterated sha512 stream seeded by the system authseed
#
# This script should be compatible with both python2 and python3
#

#
# Usage: $0 [-f /path/to/authseed] <unique-key-name> [CURVE]
#

# TODO: Error handling when authseed is missing

import sys
import os
from hashlib import sha512
import hmac
import ecdsa

#
# Iterated SHA-512 HMAC based PRNG
#
class hmac_prng():
	def __init__(self, seed):
		self.pos   = 0
		self.init  = sha512(seed).digest()
		self.state = sha512(self.init).digest()
		self.block = hmac.new(self.init, self.state, sha512).digest()

	def __call__(self, nbytes):
		buf = b''
		while (len(buf) < nbytes):
			# bytes required?
			rbytes = nbytes - len(buf)
			# bytes remaining in this state (block)
			abytes = 64 - (self.pos % 64)

			if (abytes > 0):
				buf += self.block[(self.pos%64):min(rbytes,abytes)]
				self.pos += min(rbytes,abytes);

			if (self.pos % 64 == 0):
				self.state = sha512(self.state).digest()
				self.block = hmac.new(self.init, self.state, sha512).digest()

			return buf

curves = {x.name: x for x in ecdsa.curves.curves}

def usage():
	sys.stderr.write("\n")
	sys.stderr.write("Usage: %s [-f /path/to/authseed] <unique-key-name> [CURVE]\n" % (sys.argv[0]))
	sys.stderr.write("\n")
	sys.stderr.write(" CURVE defaults to NIST256p if not specified\n")
	sys.stderr.write(" Supported curves are %s\n" % (', '.join([x for x in curves])))
	sys.stderr.write("\n")

if len(sys.argv) < 2:
    usage();
    sys.exit(1)

# Accept -f /path/to/authseed on the command line
if (sys.argv[1] == '-f'):
    sys.argv.pop(1)
    authseed = sys.argv[1]
    sys.argv.pop(1)
    # Check again if we stole some
    if (len(sys.argv) < 2):
        usage()
else:
    # Use the default authseed path when it's not provided on the command line
    authseed = '/etc/sysconfig/authseed'

# Pull the mandator name from the command line
name = sys.argv[1]
sys.argv.pop(1)

# Accept optional curve on command line
if len(sys.argv) > 1:
    curve = sys.argv[1]
    sys.argv.pop(1)
else:
    curve = 'NIST256p'

if not curve in curves:
    sys.stderr.write("\n !! Unknown curve '%s'\n" % curve)
    usage()
    sys.exit(1)

# Check for excess arguments
if len(sys.argv) != 1:
    usage()
    sys.exit(1)


# Monkey patch os.urandom
seed = open(authseed, 'rb').read();
seed = hmac.new(seed, name.encode(sys.stdin.encoding or 'UTF-8'), sha512).digest()
os.__dict__['urandom'] = hmac_prng(seed)


key = ecdsa.SigningKey.generate(curve=curves[curve])

sys.stdout.write(key.to_pem().decode('utf8'))
