#!/usr/bin/python3 -BbbEIsSttW all

"""This software is provided by the copyright owner "as is" and
without any expressed or implied warranties, including, but not
limited to, the implied warranties of merchantability and fitness
for a particular purpose are disclaimed. In no event shall the
copyright owner be liable for any direct, indirect, incidential,
special, exemplary or consequential damages, including, but not
limited to, procurement of substitute goods or services, loss
of use, data or profits or business interruption, however caused
and on any theory of liability, whether in contract, strict liability,
or tort, including negligence or otherwise, arising in any way
out of the use of this software, even if advised of the possibility
of such damage.

Copyright (c) 2019 halfdog <me (%) halfdog.net>

See https://www.halfdog.net/Blog/2019/EximSslClientAuthWithNondefaultCertificateFields/
for more information.


This tool takes the name of a ASN1 DER encoded field as formatted
by Exim (name if known, OID otherwise). The next argument is
the DER-encoded value as formatted by Exim (#[hex] for UTF8-string
values. This argument has to start with the field name from the
first argument as prefix while the hex-decoded string after the
prefix has to match the third argument.

Due to the fact that Exim string encoding, quoting, substring
matching requires a very complex syntax, all those operations
are performed within this program. Exim just base64-encodes all
arguments to avoid shell command injection risks."""

import base64
import binascii
import sys


def safeDecode(encodedStr):
  """Perform decoding of Exim call arguments."""
  decoded = base64.b64decode(encodedStr)
  if decoded[0] != 0x41:
    raise Exception()
  return decoded[1:]

def mainFunction():
  """Compare the arguments, crash or exit with error on any data
  inconsistency, bad encoding, ... thus failing authentication.
  Only on a clean match the program will terminate without errors."""

  if len(sys.argv) != 4:
    sys.exit(1)

  derName = sys.argv[1]
  encodedStr = safeDecode(sys.argv[2])
  nativeStr = safeDecode(sys.argv[3])

  namePrefix = bytes(derName, 'utf-8') + b'=#'
  if not encodedStr.startswith(namePrefix):
    raise Exception('Encoded %s does not start with expected prefix %s' % (
        repr(encodedStr), repr(derName)))

  encodedStr = binascii.unhexlify(encodedStr[len(namePrefix):])
  if (len(encodedStr) < 2) or (encodedStr[0] != 0xc):
    raise Exception('Malformed encoded string %s' % repr(encodedStr))
  lengthField = encodedStr[1]
  if ((lengthField & 0x80) != 0) or (lengthField+2 != len(encodedStr)):
    raise Exception('Invalid length encoding in %s' % repr(encodedStr))
  if encodedStr[2:] != nativeStr:
    sys.exit(1)

if __name__ == '__main__':
  mainFunction()
