#!/usr/bin/env python

# SmartPGP : JavaCard implementation of OpenPGP card v3 specification
# https://github.com/ANSSI-FR/SmartPGP
# Copyright (C) 2016 ANSSI

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

from smartcard.Exceptions import NoCardException
from smartcard.System import readers
from smartcard.util import toHexString

import struct

SELECT = [0x00, 0xA4, 0x04, 0x00,
          0x06,
          0xD2, 0x76, 0x00, 0x01, 0x24, 0x01,
          0x00]

VERIFY = [0x00, 0x20, 0x00, 0x83]
TERMINATE = [0x00, 0xe6, 0x00, 0x00]
ACTIVATE = [0x00, 0x44, 0x00, 0x00]
ACTIVATE_FULL = [0x00, 0x44, 0x00, 0x01]
GET_SM_CURVE_OID = [0x00, 0xca, 0x00, 0xd4]
GENERATE_ASYMETRIC_KEYPAIR = [0x00, 0x47, 0x80, 0x00]

ALGS_ALIASES = {
    'ansix9p256r1': 'ansix9p256r1',
    'P256': 'ansix9p256r1',
    'P-256': 'ansix9p256r1',
    'NIST-P256': 'ansix9p256r1',
    'ansix9p384r1': 'ansix9p384r1',
    'P384': 'ansix9p384r1',
    'P-384': 'ansix9p384r1',
    'NIST-P384': 'ansix9p384r1',
    'ansix9p521r1': 'ansix9p521r1',
    'P521': 'ansix9p521r1',
    'P-521': 'ansix9p521r1',
    'NIST-P521': 'ansix9p521r1',

    'brainpoolP256r1': 'brainpoolP256r1',
    'BP256': 'brainpoolP256r1',
    'BP-256': 'brainpoolP256r1',
    'brainpool256': 'brainpoolP256r1',
    'brainpoolP384r1': 'brainpoolP384r1',
    'BP384': 'brainpoolP384r1',
    'BP-384': 'brainpoolP384r1',
    'brainpool384': 'brainpoolP384r1',
    'brainpoolP512r1': 'brainpoolP512r1',
    'BP512': 'brainpoolP512r1',
    'BP-512': 'brainpoolP512r1',
    'brainpool512': 'brainpoolP512r1',
}

OID_ALGS = {
    'ansix9p256r1': [0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07],
    'ansix9p384r1': [0x2B, 0x81, 0x04, 0x00, 0x22],
    'ansix9p521r1': [0x2B, 0x81, 0x04, 0x00, 0x23],
    'brainpoolP256r1': [0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07],
    'brainpoolP384r1': [0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B],
    'brainpoolP512r1': [0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D],
}

class WrongKeyRole(Exception):
    pass

class WrongAlgo(Exception):
    pass

def ascii_encode_pin(pin):
    return [ord(c) for c in pin]

def assemble_with_len(prefix,data):
    return prefix + [len(data)] + data

def asOctets(bs):
    l = len(bs)
    if l%8 is not 0:
        raise "BitString length is not a multiple of 8"
    result = []
    i = 0
    while i < l:
        byte = 0
        for x in range(8):
            byte |= bs[i + x] << (7 - x)
        result.append(byte)
        i += 8
    return result

def encode_len(data):
    l = len(data)
    if l > 0xff:
        l = [0x82, (l >> 8) & 0xff, l & 0xff]
    elif l > 0x7f:
        l = [0x81, l & 0xff]
    else:
        l = [l & 0xff]
    return l

def _raw_send_apdu(connection, text, apdu):
    print "%s" % text
    data, sw1, sw2 = connection.transmit(apdu)
    print data
    print "%s: %02X %02X" % (text, sw1, sw2)
    return (data,sw1,sw2)

def list_readers():
    for reader in readers():
        try:
            connection = reader.createConnection()
            connection.connect()
            print(reader, toHexString(connection.getATR()))
        except NoCardException:
            print(reader, 'no card inserted')

def select_reader(reader_index):
    reader_list = readers()
    r = reader_list[reader_index]
    conn = r.createConnection()
    conn.connect()
    return conn

def select_applet(connection):
    return _raw_send_apdu(connection,"Select OpenPGP Applet",SELECT)

def verif_admin_pin(connection, admin_pin):
    verif_apdu = assemble_with_len(VERIFY,ascii_encode_pin(admin_pin))
    return _raw_send_apdu(connection,"Verify Admin PIN",verif_apdu)

def full_reset_card(connection):
    _raw_send_apdu(connection,"Terminate",TERMINATE)
    _raw_send_apdu(connection,"Activate",ACTIVATE_FULL)

def reset_card(connection):
    _raw_send_apdu(connection,"Terminate",TERMINATE)
    _raw_send_apdu(connection,"Activate",ACTIVATE)

def switch_crypto_rsa(connection,key_role):
    data = [
        0x01,       # RSA
        0x08, 0x00, # 2048 bits modulus
        0x00, 0x11, # 65537 - 17 bits public exponent
        0x03]       # crt form with modulus
    if key_role == 'sig':
        role = 0xc1
    elif key_role == 'dec':
        role = 0xc2
    elif key_role == 'auth':
        role = 0xc3
    elif key_role == 'sm':
        role = 0xd4
    else:
        raise WrongKeyRole
    prefix = [0x00, 0xDA, 0x00] + [role]
    apdu = assemble_with_len(prefix, data)
    _raw_send_apdu(connection,"Switch to RSA2048 (%s)" % (key_role,),apdu)

def switch_crypto(connection,crypto,key_role):
    alg_name = None
    role = None
    # treat RSA differently
    if crypto=='rsa2048' or crypto=='RSA2048' or crypto=='rsa' or crypto=='RSA':
        return switch_crypto_rsa(connection,key_role)
    # this code is only for elliptic curves
    try:
        alg_name = ALGS_ALIASES[crypto]
    except KeyError:
        raise WrongAlgo
    data = OID_ALGS[alg_name]
    byte1 = 0x12
    if key_role == 'sig':
        role = 0xc1
        byte1 = 0x13
    elif key_role == 'dec':
        role = 0xc2
    elif key_role == 'auth':
        role = 0xc3
    elif key_role == 'sm':
        role = 0xd4
    else:
        raise WrongKeyRole
    prefix = [0x00, 0xDA, 0x00] + [role]
    apdu = assemble_with_len(prefix, [byte1] + data + [0xff])
    _raw_send_apdu(connection,"Switch to %s (%s)" % (crypto,key_role),apdu)

def generate_sm_key(connection):
    apdu = assemble_with_len(GENERATE_ASYMETRIC_KEYPAIR, [0xA6, 0x00])
    apdu = apdu + [0x00]
    return _raw_send_apdu(connection,"Generate SM key",apdu)

def set_resetting_code(connection, resetting_code): 
    apdu = assemble_with_len([0x00, 0xDA, 0x00, 0xD3], ascii_encode_pin(resetting_code))
    _raw_send_apdu(connection,"Define the resetting code (PUK)",apdu)

def unblock_pin(connection, resetting_code, new_user_pin):
    data = ascii_encode_pin(resetting_code)+ascii_encode_pin(new_user_pin)
    apdu = assemble_with_len([0x00, 0x2C, 0x00, 0x81], data)
    _raw_send_apdu(connection,"Unblock user PIN with resetting code",apdu)

def put_sm_key(connection, pubkey, privkey):
    ins_p1_p2 = [0xDB, 0x3F, 0xFF]
    cdata = [0x92] + encode_len(privkey) + [0x99] + encode_len(pubkey)
    cdata = [0xA6, 0x00, 0x7F, 0x48] + encode_len(cdata) + cdata
    cdata = cdata + [0x5F, 0x48] + encode_len(privkey + pubkey) + privkey + pubkey
    cdata = [0x4D] + encode_len(cdata) + cdata
    i = 0
    cl = 255
    l = len(cdata)
    while i < l:
        if (l - i) <= cl:
            cla = 0x00
            data = cdata[i:]
            i = l
        else:
            cla = 0x10
            data = cdata[i:i+cl]
            i = i + cl
        apdu = assemble_with_len([cla] + ins_p1_p2, data)
        _raw_send_apdu(connection,"Sending SM key chunk",apdu)

def put_sm_certificate(connection, cert):
    prefix = [0x00, 0xA5, 0x03, 0x04]
    data = [0x60, 0x04, 0x5C, 0x02, 0x7F, 0x21]
    apdu = assemble_with_len(prefix, data)
    _raw_send_apdu(connection,"Selecting SM certificate",apdu)
    ins_p1_p2 = [0xDA, 0x7F, 0x21]
    i = 0
    cl = 255
    l = len(cert)
    while i < l:
        if (l - i) <= cl:
            cla = 0x00
            data = cert[i:]
            i = l
        else:
            cla = 0x10
            data = cert[i:i+cl]
            i = i + cl
        apdu = assemble_with_len([cla] + ins_p1_p2, data)
        _raw_send_apdu(connection,"Sending SM certificate chunk",apdu)

def get_sm_certificate(connection):
    prefix = [0x00, 0xA5, 0x03, 0x04]
    data = [0x60, 0x04, 0x5C, 0x02, 0x7F, 0x21]
    apdu = assemble_with_len(prefix, data)
    _raw_send_apdu(connection,"Selecting SM certificate",apdu)
    apdu = [0x00, 0xCA, 0x7F, 0x21, 0x00]
    (data,sw1,sw2) = _raw_send_apdu(connection,"Receiving SM certificate chunk",apdu)
    while sw1 == 0x61:
        apdu = [0x00, 0xC0, 0x00, 0x00, 0x00]
        (ndata,sw1,sw2) = _raw_send_apdu(connection,"Receiving SM certificate chunk",apdu)
        data = data + ndata
    return (data,sw1,sw2)

def get_sm_curve_oid(connection):
    """ Get Curve OID for Secure Messaging
        Return Curve OID (DER-encoded)
    """
    apdu = GET_SM_CURVE_OID + [0x00]
    (data,sw1,sw2) = _raw_send_apdu(connection,"SM Curve OID",apdu)
    b = bytearray(data)
    assert(b[0]==0xd4)
    curve_len = b[1]
    curve = b[2:]
    assert(curve_len == len(curve))
    assert(curve[0])==0x12
    curve = curve[1:]
    if curve[-1] == 0xff:
        curve.pop()
    #print ' '.join('{:02X}'.format(c) for c in curve)
    # Add DER OID header manually ...
    return '\x06' + struct.pack('B',len(curve)) + curve
