Commit 41c2ecdf authored by Arturo Hernandez's avatar Arturo Hernandez

major refactor

parent 473c3414
......@@ -32,6 +32,16 @@ python3 src/trayicon.py
puede ejecutar este icono tambien con `MST.desktop` desde la carpeta del repositorio.
### Depuración
puede iniciar el microservicio en modo de depuración (con detección de cambios de tornado y bitácora detallada en consola).
```sh
DEBUG=True LOGLEVEL=Debug LOGFILE=/dev/stdout python3 src/main.py
```
## Docker
[readme](docker/README.md)
......
......@@ -12,6 +12,9 @@ MST_PORT = int(os.environ.get("MST_PORT", '8000'))
MST_CERT = os.environ.get("MST_CERT", 'cert/server.crt')
MST_KEY = os.environ.get("MST_KEY", 'cert/server.key')
#limitar x-origin cuando se utiliza claves propias
ALLOW_ORIGIN = os.environ.get("ALLOW_ORIGIN", "*")
DEBUG = str2bool(os.environ.get("DEBUG", 'False'))
DEFAULT_KEY_A = os.environ.get("INIT_KEY_A", "FFFFFFFFFFFF") #parsed later
......
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" Initialize and handle logging for system"""
import os
import sys
import logging
from threading import Thread
#change CWD to parent of src/
os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
#print (os.getcwd())
#configuración inicia de bitácora
LEVEL_NAME = os.getenv('LOGLEVEL', "INFO").upper()
if hasattr(logging, LEVEL_NAME):
LOG_LEVEL = getattr(logging, LEVEL_NAME)
else:
LOG_LEVEL = logging.INFO # default
##if __name__ == '__main__': #logging para los modulos
logging.addLevelName(logging.DEBUG, "\033[1;36m%s\033[1;0m" % "DBG")
logging.addLevelName(logging.INFO, "\033[1;34m%s\033[1;0m" % "INF")
logging.addLevelName(logging.WARNING, "\033[1;33m%s\033[1;0m" % "WRN")
logging.addLevelName(logging.ERROR, "\033[1;41m%s\033[1;0m" % "ERR")
LOG_FILE = os.getenv('LOGFILE', 'bitacora.log')
if not os.path.isfile(LOG_FILE):
try:
open(LOG_FILE, 'a').close()
except:
pass #can't create log file
if os.path.isfile(LOG_FILE) and os.access(LOG_FILE, os.W_OK):
logging.basicConfig(
format='%(levelname)s %(asctime)s %(name)s - %(message)s',
datefmt='\033[1;37m%Y-%m-%d %H:%M:%S\033[1;0m',
filename=LOG_FILE, level=LOG_LEVEL)
else:
logging.basicConfig(
format='%(levelname)s %(asctime)s %(name)s - %(message)s',
datefmt='\033[1;37m%Y-%m-%d %H:%M:%S\033[1;0m',
level=LOG_LEVEL)
print ("WRN: LOG to STDOUT")
LOGGER = logging.getLogger(__name__)
def handle_exception(exc_type, exc_value, exc_traceback):
"""captura excepciones en la misma bitácora"""
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
LOGGER.error("Uncaught exception",
exc_info=(exc_type, exc_value, exc_traceback))
sys.excepthook = handle_exception
INIT_OLD = Thread.__init__
def init(self, *args, **kwargs):
"""nuevo inicio con hook de captura de error para log"""
INIT_OLD(self, *args, **kwargs)
run_old = self.run
def run_with_except_hook(*args, **kw):
"""run con hook para captura de error"""
try:
run_old(*args, **kw)
except (KeyboardInterrupt, SystemExit):
raise # sale finalmente
except:
sys.excepthook(*sys.exc_info()) #dispara hook
self.run = run_with_except_hook
Thread.__init__ = init
if __name__ == '__main__':
LOGGER.debug("hello debug")
LOGGER.info("hello info")
LOGGER.warn("hello warn")
LOGGER.error("hello error")
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import logging
import datetime
import traceback
from time import sleep
from threading import Thread
import tornado.httpserver
import tornado.ioloop
import tornado.web
......@@ -16,62 +14,15 @@ import tornado.web
from smartcard.util import toHexString, toASCIIString, padd
from smartcard.Exceptions import SmartcardException
from smartcard.sw.SWExceptions import SWException
#change CWD to parent of src/
os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
#print (os.getcwd())
#configuración inicia de bitácora
LEVEL_NAME = os.getenv('LOGLEVEL', "INFO").upper()
if hasattr(logging, LEVEL_NAME):
LOG_LEVEL = getattr(logging, LEVEL_NAME)
else:
LOG_LEVEL = logging.INFO # default
if __name__ == '__main__': #logging para los modulos
logging.addLevelName(logging.DEBUG, "\033[1;36m%s\033[1;0m" % "DBG")
logging.addLevelName(logging.INFO, "\033[1;34m%s\033[1;0m" % "INF")
logging.addLevelName(logging.WARNING, "\033[1;33m%s\033[1;0m" % "WRN")
logging.addLevelName(logging.ERROR, "\033[1;41m%s\033[1;0m" % "ERR")
LOG_FILE = os.getenv('LOGFILE', 'bitacora.log')
if not os.path.isfile(LOG_FILE):
try:
open(LOG_FILE, 'a').close()
except:
pass #can't create log file
if os.path.isfile(LOG_FILE) and os.access(LOG_FILE, os.W_OK):
logging.basicConfig(
format='%(levelname)s %(asctime)s %(name)s - %(message)s',
datefmt='\033[1;37m%Y-%m-%d %H:%M:%S\033[1;0m',
filename=LOG_FILE, level=LOG_LEVEL)
else:
logging.basicConfig(
format='%(levelname)s %(asctime)s %(name)s - %(message)s',
datefmt='\033[1;37m%Y-%m-%d %H:%M:%S\033[1;0m',
level=LOG_LEVEL)
print ("WRN: LOG to STDOUT")
#local modules
if __name__ == "__main__":
import log_init
LOGGER = logging.getLogger(__name__)
def handle_exception(exc_type, exc_value, exc_traceback):
"""captura excepciones en la misma bitácora"""
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
LOGGER.error("Uncaught exception",
exc_info=(exc_type, exc_value, exc_traceback))
sys.excepthook = handle_exception
INIT_OLD = Thread.__init__
def init(self, *args, **kwargs):
"""nuevo inicio con hook de captura de error para log"""
INIT_OLD(self, *args, **kwargs)
run_old = self.run
def run_with_except_hook(*args, **kw):
"""run con hook para captura de error"""
try:
run_old(*args, **kw)
except (KeyboardInterrupt, SystemExit):
raise # sale finalmente
except:
sys.excepthook(*sys.exc_info()) #dispara hook
self.run = run_with_except_hook
Thread.__init__ = init
from parseATR import parseATR #https://github.com/LudovicRousseau/pyscard-contrib
import config
......@@ -98,25 +49,28 @@ def find_atr(data, atr):
for d in found[a]:
data["ATR_possible"].append(str(d))
class MainHandler(tornado.web.RequestHandler):
def get(self):
LOGGER.debug(self.request)
self.write("Hello, world")
class ReadCardHandler(tornado.web.RequestHandler):
class BaseHandler(tornado.web.RequestHandler):
def set_default_headers(self):
self.set_header("Access-Control-Allow-Origin", "*")
self.set_header('Access-Control-Allow-Methods', 'GET, OPTIONS')
""" for CORS. necesario para microservicio en local"""
#build list:
methods = ["head", "get", "post", "delete", "patch", "put", "options"]
filtered = [mn.upper() for mn in methods if not getattr(self, mn, None) == self._unimplemented_method]
self.set_header("Access-Control-Allow-Origin", config.ALLOW_ORIGIN)
self.set_header('Access-Control-Allow-Methods', ', '.join(filtered))
self.set_header("Access-Control-Allow-Headers", "x-requested-with,access-control-allow-origin,authorization,content-type")
remote_ip = self.request.headers.get("X-Real-IP") or \
self.request.headers.get("X-Forwarded-For") or \
self.request.remote_ip
LOGGER.info("{} from {}".format(self.request.method, remote_ip))
def options(self):
# no body
""" for CORS. return only headers """
self.set_status(204)
# no body
self.finish()
class ReadCardHandler(BaseHandler):
def initialize(self): #must override with actual endpoints
self.endpoints = ["GET"] #TODO: get from class inheritage (if possible)
def get(self):
print("reading card...")
block = int(self.get_argument("block",default=-1,strip=True))
......@@ -158,23 +112,13 @@ class ReadCardHandler(tornado.web.RequestHandler):
if value is not None:
data["data"]["isValue"] = True
data["data"]["value"] = value[0] #TODO: adr?
else:
data["message"] = "Sin autorización para leer bloque de datos"
find_atr(data, atr)
self.write(data)
class WriteCardHandler(tornado.web.RequestHandler):
def set_default_headers(self):
self.set_header("Access-Control-Allow-Origin", "*")
self.set_header('Access-Control-Allow-Methods', 'POST, OPTIONS')
self.set_header("Access-Control-Allow-Headers", "x-requested-with,access-control-allow-origin,authorization,content-type")
remote_ip = self.request.headers.get("X-Real-IP") or \
self.request.headers.get("X-Forwarded-For") or \
self.request.remote_ip
LOGGER.info("{} from {}".format(self.request.method, remote_ip))
def options(self):
# no body
self.set_status(204)
self.finish()
class WriteCardHandler(BaseHandler):
def post(self):
print("writing card...")
block = int(self.get_argument("block",strip=True))
......@@ -206,20 +150,8 @@ class WriteCardHandler(tornado.web.RequestHandler):
find_atr(data, atr)
self.write(data)
class InitCardHandler(tornado.web.RequestHandler):
class InitCardHandler(BaseHandler):
""" WIP mf plus"""
def set_default_headers(self):
self.set_header("Access-Control-Allow-Origin", "*")
self.set_header('Access-Control-Allow-Methods', 'POST, OPTIONS')
self.set_header("Access-Control-Allow-Headers", "x-requested-with,access-control-allow-origin,authorization,content-type")
remote_ip = self.request.headers.get("X-Real-IP") or \
self.request.headers.get("X-Forwarded-For") or \
self.request.remote_ip
LOGGER.info("{} from {}".format(self.request.method, remote_ip))
def options(self):
# no body
self.set_status(204)
self.finish()
def post(self):
print("init card...")
try:
......@@ -250,6 +182,11 @@ class InitCardHandler(tornado.web.RequestHandler):
find_atr(data, atr)
self.write(data)
class MainHandler(tornado.web.RequestHandler):
def get(self):
LOGGER.debug(self.request)
self.write("Hello, world")
class IndexHandler(tornado.web.RequestHandler):
def get(self, url):
LOGGER.info("indexHandler")
......
......@@ -51,7 +51,7 @@ class OMNIKEYException(Exception):
class OMNIKEYCard(object):
""" clase para lector HID OMNIKEY """
@staticmethod
def connect():
def connect() -> object:
# get all the available readers
try:
list_readers = readers()
......@@ -89,11 +89,11 @@ class OMNIKEYCard(object):
raise OMNIKEYException('Tarjeta bloqueada. Reintente')
card = OMNIKEYCard(reader, connection)
return card
def __init__(self, reader, connection):
def __init__(self, reader, connection) -> None:
self.reader = reader
self.connection = connection
def MP_BO_Init(self):
def MP_BO_Init(self) -> None:
LOGGER.debug("init")
inited = False
try:
......@@ -145,7 +145,7 @@ class OMNIKEYCard(object):
return inited
@staticmethod
def parse_value(block):
def parse_value(block) -> (int, int):
value = None
#check if value
if len(block) != 16:
......@@ -160,20 +160,20 @@ class OMNIKEYCard(object):
LOGGER.debug("not a value block...")
return value
def APDU_get_version(self):
def APDU_get_version(self) -> str:
data, sw1, sw2 = self.connection.transmit([0xFF, 0x68, 0x0E, 0x08, 0x02, 0x01, 0x00]) # propietary reader command "get version"
LOGGER.debug('VERSION {} sw: {}'.format(toHexString(data), toHexString([sw1, sw2])))
self.sw1 = sw1; self.sw2 = sw2
errorchain[0](data, sw1, sw2)
return toHexString(data) # 01 00
def APDU_enter_transparent_session(self):
def APDU_enter_transparent_session(self) -> None:
data, sw1, sw2 = self.connection.transmit([0xFF, 0x68, 0x0E, 0x06, 0x01, 0x00]) # propietary reader command "read uid"
LOGGER.debug('ETS {} sw: {}'.format(toHexString(data), toHexString([sw1, sw2])))
self.sw1 = sw1; self.sw2 = sw2
errorchain[0](data, sw1, sw2)
def APDU_transcieve(self, command, txrx_flags=0x0A, valid_bits=0, timeout_us=1084800):
def APDU_transcieve(self, command, txrx_flags=0x0A, valid_bits=0, timeout_us=1084800) -> list:
TRANSCIEVE = [
0xFF, 0x68,
0x0E, 0x03,
......@@ -187,57 +187,56 @@ class OMNIKEYCard(object):
errorchain[0](data, sw1, sw2)
return data[2:]
def APDU_set_protocol(self, type=0x02):
def APDU_set_protocol(self, type=0x02) -> None:
data, sw1, sw2 = self.connection.transmit([0xFF, 0x68, 0x0E, 0x00, 0x01, type])
LOGGER.debug('SETP {} sw: {}'.format(toHexString(data), toHexString([sw1, sw2])))
self.sw1 = sw1; self.sw2 = sw2
errorchain[0](data, sw1, sw2)
def APDU_set_field(self, field=0x01):
def APDU_set_field(self, field=0x01) -> None:
data, sw1, sw2 = self.connection.transmit([0xFF, 0x68, 0x0E, 0x02, 0x01, field])
LOGGER.debug('SETF {} sw: {}'.format(toHexString(data), toHexString([sw1, sw2])))
self.sw1 = sw1; self.sw2 = sw2
errorchain[0](data, sw1, sw2)
def APDU_set_speed(self, tx_speed=0x00, rx_speed=0x00):
def APDU_set_speed(self, tx_speed=0x00, rx_speed=0x00) -> None:
data, sw1, sw2 = self.connection.transmit([0xFF, 0x68, 0x0E, 0x01, 0x03, tx_speed, rx_speed, 0x00])
LOGGER.debug('SETS {} sw: {}'.format(toHexString(data), toHexString([sw1, sw2])))
self.sw1 = sw1; self.sw2 = sw2
errorchain[0](data, sw1, sw2)
def APDU_exit_transparent_session(self):
def APDU_exit_transparent_session(self) -> None:
data, sw1, sw2 = self.connection.transmit([0xFF, 0x68, 0x0E, 0x07, 0x01, 0x00])
LOGGER.debug('XTS {} sw: {}'.format(toHexString(data), toHexString([sw1, sw2])))
self.sw1 = sw1; self.sw2 = sw2
errorchain[0](data, sw1, sw2)
def get_ATR(self):
def get_ATR(self) -> list:
return self.connection.getATR()
def get_UID(self):
def get_UID(self) -> list:
data, sw1, sw2 = self.connection.transmit([0xFF, 0xCA, 0x00, 0x00, 0x00]) # propietary reader command "read uid"
LOGGER.debug('getUID {} sw: {}'.format(toHexString(data), toHexString([sw1, sw2])))
self.sw1 = sw1; self.sw2 = sw2
errorchain[0](data, sw1, sw2)
return data
def send_key(self, key=DEFAULT_KEY, keynumber=0):
def send_key(self, key=DEFAULT_KEY, keynumber=0) -> None:
""" send key TODO: check key size, 0x20 = nvmem key"""
LOAD_KEY = [0xFF, 0x82, 0x20, keynumber, len(key)]+ key # must be vector
data, sw1, sw2 = self.connection.transmit(LOAD_KEY)
LOGGER.debug('KEY#{} {} sw: {}'.format(keynumber, toHexString(key), toHexString([sw1, sw2])))
self.sw1 = sw1; self.sw2 = sw2
errorchain[0](data, sw1, sw2)
#todo: raise error if sw not 90 00
def auth_block(self, block, keytype=TYPE_KEYA, keynumber=0):
def auth_block(self, block, keytype=TYPE_KEYA, keynumber=0) -> None:
AUTH = [0xFF, 0x86, 0x00, 0x00, 0x05, 0x01, 0x00, block, keytype, keynumber]
data, sw1, sw2 = self.connection.transmit(AUTH)
LOGGER.debug('AUTH:#{}:{}:{} sw: {}'.format(block, keynumber, toHexString(AUTH), toHexString([sw1, sw2])))
self.sw1 = sw1; self.sw2 = sw2
errorchain[0](data, sw1, sw2)
def auth_with_keys(self, block, key_a="FFFFFFFFFFFF", key_b="FFFFFFFFFFFF", use_keyb=False):
def auth_with_keys(self, block, key_a="FFFFFFFFFFFF", key_b="FFFFFFFFFFFF", use_keyb=False) -> bool:
key_a = list(bytes.fromhex(key_a)) #TODO: check if already list
key_b = list(bytes.fromhex(key_b))
self.send_key(key_a, 0)
......@@ -253,23 +252,23 @@ class OMNIKEYCard(object):
try:
if use_keyb:
LOGGER.warn("Trying keyA")
card.auth_block(block, TYPE_KEYA, 0)
self.auth_block(block, TYPE_KEYA, 0)
else:
LOGGER.warn("Trying keyB")
card.auth_block(block, TYPE_KEYB, 1)
self.auth_block(block, TYPE_KEYB, 1)
except SWException as e:
LOGGER.error("Can't auth with keys. %s", e)
return False
return True
def read_block(self, block):
def read_block(self, block) -> list:
data, sw1, sw2 = self.connection.transmit([0xFF, 0xB0, 0x00 , block, 0x00]) # read block
LOGGER.debug ("RB#{}, data:{}, sw {}". format(block, toHexString(data), toHexString([sw1, sw2])))
self.sw1 = sw1; self.sw2 = sw2
errorchain[0](data, sw1, sw2)
return data
def write_block(self, block, data):
def write_block(self, block, data) -> None:
if (block % 4) == 3:
LOGGER.debug ("Intentando escribir bloque de llaves #{}". format(block))
raise OMNIKEYException("bloqueado acceso a llaves")
......@@ -281,7 +280,7 @@ class OMNIKEYCard(object):
self.sw1 = sw1; self.sw2 = sw2
errorchain[0](response, sw1, sw2)
def write_value(self, block, value, addr=0):
def write_value(self, block, value, addr=0) -> None:
if (block % 4) == 3:
LOGGER.debug ("Intentando escribir bloque de llaves #{}". format(block))
raise OMNIKEYException("bloqueado acceso a llaves")
......@@ -293,17 +292,17 @@ class OMNIKEYCard(object):
self.write_block(block, data)
@staticmethod
def isTrailer(block):
def isTrailer(block) -> bool:
return (block & 0x03) == 0x03 if block < 128 else (block & 0x0F) == 0x0F
def read_value(self, block):
def read_value(self, block) -> int:
data = self.read_block(block)
parsed = unpack("<IIIBBBB", bytearray(data))
LOGGER.debug ("RV values: {}". format(parsed))
#verify data integrity?
return self.parse_value(data)
def increment_value(self, block, value_inc):
def increment_value(self, block, value_inc) -> None:
if self.isTrailer(block):
LOGGER.debug ("Intentando escribir bloque de llaves #{}". format(block))
raise OMNIKEYException("bloqueado acceso a llaves")
......@@ -314,7 +313,7 @@ class OMNIKEYCard(object):
self.sw1 = sw1; self.sw2 = sw2
errorchain[0](response, sw1, sw2)
def decrement_value(self, block, value_dec):
def decrement_value(self, block, value_dec) -> None:
if self.isTrailer(block):
LOGGER.debug ("Intentando escribir bloque de llaves #{}". format(block))
raise OMNIKEYException("bloqueado acceso a llaves")
......@@ -325,7 +324,7 @@ class OMNIKEYCard(object):
self.sw1 = sw1; self.sw2 = sw2
errorchain[0](response, sw1, sw2)
def write_trailer(self, block, key_a=DEFAULT_KEY, access_bits=[0xFF, 0x07, 0x80, 0x69], key_b=DEFAULT_KEY):
def write_trailer(self, block, key_a=DEFAULT_KEY, access_bits=[0xFF, 0x07, 0x80, 0x69], key_b=DEFAULT_KEY) -> None:
if not self.isTrailer(block):
LOGGER.debug ("Intentando escribir bloque normal #{}". format(block))
raise OMNIKEYException("bloqueado acceso normal")
......
......@@ -43,6 +43,7 @@
<button id="btn2" class="btn btn-info">Inicializar +</button>
<button id="btn3" class="btn btn-info">Leer datos</button>
<button id="btn4" class="btn btn-info">Escribir!</button>
<button id="btn4" class="btn btn-info">Borrar</button>
</div>
</div>
<div class="row">
......@@ -122,7 +123,7 @@ $(function() {
$("#uiid").text('');
$("#mensaje").text('');
$("#spin").show();
$.ajax('https://127.0.0.1:8000/api/read-card', {method:'GET', data:{block:0}, timeout: 2000 }).done(function(data,st){
$.ajax('https://127.0.0.1:8000/api/read-card', {method:'GET', data:{block:3}, timeout: 2000 }).done(function(data,st){
$("#spin").hide();
console.log(data,st);
......@@ -147,7 +148,10 @@ $(function() {
$("#mensaje").removeClass('alert-danger').addClass('alert-success');
if (data.data){
$("#mensaje").text("Block " + data.data.block + " : " + (data.data.isValue ? data.data.value : data.data.string));
}else $("#mensaje").text(data.message);
}else{
$("#mensaje").removeClass('alert-success').addClass('alert-danger');
$("#mensaje").text(data.message);
}
}else{
$("#mensaje").removeClass('alert-success').addClass('alert-danger');
$("#mensaje").text(data.error || data.message || "Error al recabar respuesta " + data.toString());
......
......@@ -10,6 +10,7 @@ Requisitos:
- launcher: [autoit3](https://www.autoitscript.com/site/)
- installer: [NSIS](http://nsis.sourceforge.net/Main_Page)
- certificado SSL
## Generar Instalador! (en windows)
......@@ -25,7 +26,7 @@ Requisitos:
2. Ejecutar `build_launcher.cmd` el cual compila mediante Autoit el script ubicado en `_setup/launcher.au3`
3. Ejecutar `build_cleaner.cmd` el cual compila mediante Autoit el script ubicado en `_setup/cleaner.au3`
4. Ejecutar `build_installer.cmd` el cual compila mediante NSIS el script ubicado en `_setup/installer.nsi`
5. el instalador se genera en la misma carpeta `AGETIC_MST_installer.exe`
......@@ -69,7 +70,7 @@ Todas estas requieren la aplicación desinstalada del sistema (o simplemente que
1. Opción 1:
1. abrir la consola de python `win\python-3.6.8\scripts\cmd.bat`
2. Instalar los nuevos paquetes con `pip install paquete`
1. en caso de falla, buscar un archivo de distribución WHL equivalente (por ejemplo en appveyor) e instalar con `pip install <archivo-amd64>.whl`
1. en caso de falla, buscar un archivo de distribución WHL equivalente (por ejemplo en appveyor) e instalar con `pip install <archivo-win32>.whl`
3. se recomienda actualizar la lista de requisitos en `win\setup_requirements.txt` con el contenido de `pip freeze`
2. Opción 2:
1. Actualizar la lista de paquetes en `win\setup_requirements.txt`
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment