#!/usr/bin/python
# Copyright 2016 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Module for testing ecc functions using extended commands."""
import binascii
import hashlib
import os
import struct

import subcmd
import utils

_ECC_OPCODES = {
  'SIGN': 0x00,
  'VERIFY': 0x01,
  'KEYGEN': 0x02,
  'KEYDERIVE': 0x03,
}

_ECC_CURVES = {
  'NIST-P256': 0x03,
}

# TPM2 signature codes.
_SIGN_MODE = {
  'NONE': 0x00,
  'ECDSA': 0x18,
  # TODO(ngm): add support for SCHNORR.
  # 'SCHNORR': 0x1c
}

# TPM2 ALG codes.
_HASH = {
  'NONE': 0x00,
  'SHA1': 0x04,
  'SHA256': 0x0B
}

_HASH_FUNC = {
    'NIST-P256': hashlib.sha256
}

# Command format.
#
#   0x00 OP
#   0x00 CURVE_ID
#   0x00 SIGN_MODE
#   0x00 HASHING
#   0x00 MSB IN LEN
#   0x00 LSB IN LEN
#   .... IN
#   0x00 MSB DIGEST LEN
#   0x00 LSB DIGEST LEN
#   .... DIGEST
#
_ECC_CMD_FORMAT = '{o:c}{c:c}{s:c}{h:c}{ml:s}{msg}{dl:s}{dig}'


def _sign_cmd(curve_id, hash_func, sign_mode, msg):
  op = _ECC_OPCODES['SIGN']
  digest = hash_func(msg).digest()
  digest_len = len(digest)
  return _ECC_CMD_FORMAT.format(o=op, c=curve_id, s=sign_mode, h=_HASH['NONE'],
                               ml=struct.pack('>H', 0), msg='',
                               dl=struct.pack('>H', digest_len), dig=digest)


def _verify_cmd(curve_id, hash_func, sign_mode, msg, sig):
  op = _ECC_OPCODES['VERIFY']
  sig_len = len(sig)
  digest = hash_func(msg).digest()
  digest_len = len(digest)
  return _ECC_CMD_FORMAT.format(o=op, c=curve_id, s=sign_mode, h=_HASH['NONE'],
                               ml=struct.pack('>H', sig_len), msg=sig,
                               dl=struct.pack('>H', digest_len), dig=digest)


def _keygen_cmd(curve_id):
  op = _ECC_OPCODES['KEYGEN']
  return _ECC_CMD_FORMAT.format(o=op, c=curve_id, s=_SIGN_MODE['NONE'],
                               h=_HASH['NONE'], ml=struct.pack('>H', 0), msg='',
                               dl=struct.pack('>H', 0), dig='')


def _keyderive_cmd(curve_id, seed):
  op = _ECC_OPCODES['KEYDERIVE']
  seed_len = len(seed)
  return _ECC_CMD_FORMAT.format(o=op, c=curve_id, s=_SIGN_MODE['NONE'],
                               h=_HASH['NONE'], ml=struct.pack('>H', seed_len),
                               msg=seed, dl=struct.pack('>H', 0), dig='')


_SIGN_INPUTS = (
  ('NIST-P256', 'ECDSA'),
)


_KEYGEN_INPUTS = (
  ('NIST-P256',),
)


_KEYDERIVE_INPUTS = (
  # Curve-id, random seed size.
  ('NIST-P256', 32),
)


def _sign_test(tpm):
  msg = 'Hello CR50'

  for data in _SIGN_INPUTS:
    curve_id, sign_mode = data
    test_name = 'ECC-SIGN:%s:%s' % data
    cmd = _sign_cmd(_ECC_CURVES[curve_id], _HASH_FUNC[curve_id],
                    _SIGN_MODE[sign_mode], msg)
    wrapped_response = tpm.command(tpm.wrap_ext_command(subcmd.ECC, cmd))
    signature = tpm.unwrap_ext_response(subcmd.ECC, wrapped_response)

    cmd = _verify_cmd(_ECC_CURVES[curve_id], _HASH_FUNC[curve_id],
                      _SIGN_MODE[sign_mode], msg, signature)
    wrapped_response = tpm.command(tpm.wrap_ext_command(subcmd.ECC, cmd))
    verified = tpm.unwrap_ext_response(subcmd.ECC, wrapped_response)
    expected = '\x01'
    if verified != expected:
      raise subcmd.TpmTestError('%s error:%s:%s' % (
        test_name, utils.hex_dump(verified), utils.hex_dump(expected)))
    print('%sSUCCESS: %s' % (utils.cursor_back(), test_name))


def _keygen_test(tpm):
  for data in _KEYGEN_INPUTS:
    curve_id, = data
    test_name = 'ECC-KEYGEN:%s' % data
    cmd = _keygen_cmd(_ECC_CURVES[curve_id])
    wrapped_response = tpm.command(tpm.wrap_ext_command(subcmd.ECC, cmd))
    valid = tpm.unwrap_ext_response(subcmd.ECC, wrapped_response)
    expected = '\x01'
    if valid != expected:
      raise subcmd.TpmTestError('%s error:%s:%s' % (
        test_name, utils.hex_dump(valid), utils.hex_dump(expected)))
    print('%sSUCCESS: %s' % (utils.cursor_back(), test_name))


def _keyderive_test(tpm):
  for data in _KEYDERIVE_INPUTS:
    curve_id, seed_bytes = data
    seed = os.urandom(seed_bytes)
    test_name = 'ECC-KEYDERIVE:%s' % data[0]
    cmd = _keyderive_cmd(_ECC_CURVES[curve_id], seed)
    wrapped_response = tpm.command(tpm.wrap_ext_command(subcmd.ECC, cmd))
    valid = tpm.unwrap_ext_response(subcmd.ECC, wrapped_response)
    expected = '\x01'
    if valid != expected:
      raise subcmd.TpmTestError('%s error:%s:%s' % (
        test_name, utils.hex_dump(valid), utils.hex_dump(expected)))
    print('%sSUCCESS: %s' % (utils.cursor_back(), test_name))


def ecc_test(tpm):
  _sign_test(tpm)
  _keygen_test(tpm)
  _keyderive_test(tpm)
