#
# shasig.py
#	Routines for protecting content with an SHA hash
#
# These routines let two communicators with a PSK to generate and later
#  verify packets.
import sys, sha, time, json

# Running nonce we use.
# Note we're assuming enough time coherence that a restart will
#  not move back in time.
PrevNonce = 0.0

# The earliest nonce we'll accept from a peer
# We assume tolerable (NTP-level) time coherence, so we're looking
#  for only newly generated packets
# This is global; the specific nonce we'll accept from any one peer
#  is kept on a per-peer basis
OldestNonce = time.time()
TheirNonces = {}

# Generate a new nonce for us to use
def new_nonce():
    global PrevNonce

    # Nonce based on current time
    res = time.time()

    # Collision, inch it forward
    if res <= PrevNonce:
	res += 0.0001

    # Record it to detect future collision
    PrevNonce = res
    return res

# Tell if this @inners is OK from @host with @psk and @nonce
# We record their nonces and defend against replays
def verify(host, psk, nonce, sig, inners):
    global OldestNonce, TheirNonces

    if nonce < OldestNonce:
	sys.stderr.write("Old pak from %s: %s\n" % (host, nonce))
	return False
    if nonce <= TheirNonces.get(host, 0.0):
	sys.stderr.write("%s replay: %s\n" % (host, nonce))
	return False

    # See what *we* would expect
    dig = sha.sha(inners + ',' + str(nonce) + ',' + psk).hexdigest()

    # Mismatch?
    if dig != sig:
	sys.stderr.write("%s bad sig\n" % (host,))
	return False

    # It's legit, so update latest nonce value
    TheirNonces[host] = nonce

    # Ok
    return True

# Tell the nonce and sig for this content @inners
# Returns (nonce-float, sig-str)
def sign(psk, inners):
    nonce = new_nonce()
    nonces = str(nonce)

    dig = sha.sha(inners + ',' + nonces + ',' + psk).hexdigest()

    return (nonce, dig)

# Extract standard outer message, verifying authenticity
#
# Return inner string, or None on failure
def decode_outer(who, psk, buf):
    # Extract JSON format wrapper
    try:
	msg = json.loads(buf)
	nonces = str(msg["nonce"])
	nonce = float(nonces)
	dig = str(msg["sig"])
	inners = str(msg["inner"])
    except:
	return None

    # Verify integrity of contents
    if not verify(who, psk, nonce, dig, inners):
	return None

    # Looks like you can trust it
    return inners

# Encode standard outer message, verifying authenticity
#
# Return outer wrapper as string (in the format which
#  decode_outer() would process)
def encode_outer(psk, buf):

    # Sig for inner content
    nonce,dig = sign(psk, buf)

    # Outer object
    outer = {"nonce": nonce, "sig": dig, "inner": buf}

    return json.dumps(outer)
