Agregar certificado

This commit is contained in:
Mauricio Baeza 2017-10-08 22:01:19 -05:00
parent 361aa131d3
commit f525ced698
8 changed files with 375 additions and 17 deletions

View File

@ -1,7 +1,9 @@
falcon falcon
falcon-multipart
Beaker Beaker
Mako Mako
peewee peewee
click click
logbook logbook
bcrypt bcrypt
python-dateutil

View File

@ -70,6 +70,17 @@ class AppValues(object):
req.context['result'] = self._db.get_values(table, values) req.context['result'] = self._db.get_values(table, values)
resp.status = falcon.HTTP_200 resp.status = falcon.HTTP_200
def on_post(self, req, resp, table):
file_object = req.get_param('upload')
if file_object is None:
session = req.env['beaker.session']
values = req.params
req.context['result'] = self._db.validate_cert(values, session)
else:
req.context['result'] = self._db.add_cert(file_object)
resp.status = falcon.HTTP_200
class AppPartners(object): class AppPartners(object):

View File

@ -2,17 +2,20 @@
import datetime import datetime
import getpass import getpass
import hashlib
import json import json
import mimetypes import mimetypes
import os import os
import re import re
import sqlite3 import sqlite3
import subprocess
import tempfile
import unicodedata import unicodedata
import uuid import uuid
#~ import bcrypt from dateutil import parser
from settings import log, template_lookup, COMPANIES, DB_SAT from settings import DEBUG, log, template_lookup, COMPANIES, DB_SAT
#~ def _get_hash(password): #~ def _get_hash(password):
@ -23,6 +26,17 @@ from settings import log, template_lookup, COMPANIES, DB_SAT
#~ return bcrypt.hashpw(password.encode(), hashed.encode()) == hashed.encode() #~ return bcrypt.hashpw(password.encode(), hashed.encode()) == hashed.encode()
def _call(args):
return subprocess.check_output(args, shell=True).decode()
def _save_temp(data, modo='wb'):
path = tempfile.mkstemp()[1]
with open(path, modo) as f:
f.write(data)
return path
def get_pass(): def get_pass():
password = getpass.getpass('Introduce la contraseña: ') password = getpass.getpass('Introduce la contraseña: ')
pass2 = getpass.getpass('Confirma la contraseña: ') pass2 = getpass.getpass('Confirma la contraseña: ')
@ -177,3 +191,148 @@ def to_slug(string):
.encode('ascii', 'ignore') .encode('ascii', 'ignore')
.decode('ascii').lower()) .decode('ascii').lower())
return value return value
class Certificado(object):
def __init__(self, key, cer):
self._key = key
self._cer = cer
self._modulus = ''
self._save_files()
self.error = ''
def _save_files(self):
try:
self._path_key = _save_temp(self._key)
self._path_cer = _save_temp(self._cer)
except:
self._path_key = ''
self._path_cer = ''
return
def _kill(self, path):
try:
os.remove(path)
except:
pass
return
def _get_info_cer(self, session_rfc):
data = {}
args = 'openssl x509 -inform DER -in {}'
try:
cer_pem = _call(args.format(self._path_cer))
except Exception as e:
self.error = 'No se pudo convertir el CER en PEM'
return data
args = 'openssl enc -base64 -in {}'
try:
cer_txt = _call(args.format(self._path_cer))
except Exception as e:
self.error = 'No se pudo convertir el CER en TXT'
return data
args = 'openssl x509 -inform DER -in {} -noout -{}'
try:
result = _call(args.format(self._path_cer, 'purpose')).split('\n')[3]
except Exception as e:
self.error = 'No se puede saber si es FIEL'
return data
if result == 'SSL server : No':
self.error = 'El certificado es FIEL'
return data
result = _call(args.format(self._path_cer, 'serial'))
serie = result.split('=')[1].split('\n')[0][1::2]
result = _call(args.format(self._path_cer, 'subject'))
rfc = result.split('=')[5].split('/')[0].strip()
if not DEBUG:
if not rfc == session_rfc:
self.error = 'El RFC del certificado no corresponde.'
return data
dates = _call(args.format(self._path_cer, 'dates')).split('\n')
desde = parser.parse(dates[0].split('=')[1])
hasta = parser.parse(dates[1].split('=')[1])
self._modulus = _call(args.format(self._path_cer, 'modulus'))
data['cer'] = self._cer
data['cer_tmp'] = None
data['cer_pem'] = cer_pem
data['cer_txt'] = cer_txt.replace('\n', '')
data['serie'] = serie
data['rfc'] = rfc
data['desde'] = desde
data['hasta'] = hasta
return data
def _get_p12(self, password, rfc):
tmp_cer = tempfile.mkstemp()[1]
tmp_key = tempfile.mkstemp()[1]
tmp_p12 = tempfile.mkstemp()[1]
args = 'openssl x509 -inform DER -in "{}" -out "{}"'
_call(args.format(self._path_cer, tmp_cer))
args = 'openssl pkcs8 -inform DER -in "{}" -passin pass:{} -out "{}"'
_call(args.format(self._path_key, password, tmp_key))
args = 'openssl pkcs12 -export -in "{}" -inkey "{}" -name "{}" -passout ' \
'pass:"{}" -out "{}"'
_call(args.format(tmp_cer, tmp_key, rfc,
hashlib.md5(rfc.encode()).hexdigest(), tmp_p12))
data = open(tmp_p12, 'rb').read()
self._kill(tmp_cer)
self._kill(tmp_key)
self._kill(tmp_p12)
return data
def _get_info_key(self, password, rfc):
data = {}
args = 'openssl pkcs8 -inform DER -in "{}" -passin pass:{}'
try:
result = _call(args.format(self._path_key, password))
except Exception as e:
self.error = 'Contraseña incorrecta'
return data
args = 'openssl pkcs8 -inform DER -in "{}" -passin pass:{} | ' \
'openssl rsa -noout -modulus'
mod_key = _call(args.format(self._path_key, password))
if self._modulus != mod_key:
self.error = 'Los archivos no son pareja'
return data
args = 'openssl pkcs8 -inform DER -in "{}" -passin pass:{} | ' \
'openssl rsa -des3 -passout pass:{}'.format(
self._path_key, password, hashlib.md5(rfc.encode()).hexdigest())
key_enc = _call(args)
data['key'] = self._key
data['key_tmp'] = None
data['key_enc'] = key_enc
data['p12'] = self._get_p12(password, rfc)
return data
def validate(self, password, rfc):
if not self._path_key or not self._path_cer:
self.error = 'Error al cargar el certificado'
return {}
data = self._get_info_cer(rfc)
llave = self._get_info_key(password, rfc)
if not llave:
return {}
data.update(llave)
self._kill(self._path_key)
self._kill(self._path_cer)
return data

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import falcon import falcon
from falcon_multipart.middleware import MultipartMiddleware
from beaker.middleware import SessionMiddleware from beaker.middleware import SessionMiddleware
from middleware import ( from middleware import (
@ -20,8 +21,12 @@ from settings import DEBUG
db = StorageEngine() db = StorageEngine()
api = falcon.API( api = falcon.API(middleware=[
middleware=[AuthMiddleware(), JSONTranslator(), ConnectionMiddleware()]) AuthMiddleware(),
JSONTranslator(),
ConnectionMiddleware(),
MultipartMiddleware(),
])
api.req_options.auto_parse_form_urlencoded = True api.req_options.auto_parse_form_urlencoded = True
api.add_sink(handle_404, '') api.add_sink(handle_404, '')

View File

@ -14,6 +14,15 @@ class StorageEngine(object):
def get_values(self, table, values=None): def get_values(self, table, values=None):
return getattr(self, '_get_{}'.format(table))(values) return getattr(self, '_get_{}'.format(table))(values)
def add_cert(self, file_object):
return main.Certificado.add(file_object)
def validate_cert(self, values, session):
return main.Certificado.validate(values, session)
def _get_cert(self, values):
return main.Certificado.get_data()
def _get_cp(self, values): def _get_cp(self, values):
return main.get_cp(values['cp']) return main.get_cp(values['cp'])

View File

@ -238,20 +238,77 @@ class Emisor(BaseModel):
class Certificado(BaseModel): class Certificado(BaseModel):
key = BlobField() key = BlobField(null=True)
key_tmp = BlobField(null=True)
key_enc = TextField(default='') key_enc = TextField(default='')
cer = BlobField() cer = BlobField(null=True)
cer_tmp = BlobField(null=True)
cer_pem = TextField(default='') cer_pem = TextField(default='')
cer_txt = TextField(default='') cer_txt = TextField(default='')
p12 = BlobField() p12 = BlobField(null=True)
serie = TextField(default='') serie = TextField(default='')
rfc = TextField(default='') rfc = TextField(default='')
desde = DateTimeField() desde = DateTimeField(null=True)
hasta = DateTimeField() hasta = DateTimeField(null=True)
def __str__(self): def __str__(self):
return self.serie return self.serie
@classmethod
def get_data(cls):
obj = cls.get_(cls)
row = {
'cert_rfc': obj.rfc,
'cert_serie': obj.serie,
'cert_desde': obj.desde,
'cert_hasta': obj.hasta,
}
return row
def get_(cls):
if Certificado.select().count():
obj = Certificado.select()[0]
else:
obj = Certificado()
return obj
@classmethod
def add(cls, file_object):
obj = cls.get_(cls)
if file_object.filename.endswith('key'):
obj.key_tmp = file_object.file.read()
elif file_object.filename.endswith('cer'):
obj.cer_tmp = file_object.file.read()
obj.save()
return {'status': 'server'}
@classmethod
def validate(cls, values, session):
row = {}
result = False
obj = cls.get_(cls)
cert = util.Certificado(obj.key_tmp, obj.cer_tmp)
data = cert.validate(values['contra'], session['rfc'])
if data:
msg = 'Certificado guardado correctamente'
q = Certificado.update(**data).where(Certificado.id==obj.id)
if q.execute():
result = True
row = {
'cert_rfc': data['rfc'],
'cert_serie': data['serie'],
'cert_desde': data['desde'],
'cert_hasta': data['hasta'],
}
else:
msg = cert.error
obj.key_tmp = None
obj.cer_tmp = None
obj.save()
return {'ok': result, 'msg': msg, 'data': row}
class Folios(BaseModel): class Folios(BaseModel):
serie = TextField(unique=True) serie = TextField(unique=True)

View File

@ -13,7 +13,8 @@ var controllers = {
$$('emisor_cp').attachEvent('onTimedKeyPress', emisor_postal_code_key_up) $$('emisor_cp').attachEvent('onTimedKeyPress', emisor_postal_code_key_up)
$$('chk_escuela').attachEvent('onChange', chk_escuela_change) $$('chk_escuela').attachEvent('onChange', chk_escuela_change)
$$('chk_ong').attachEvent('onChange', chk_ong_change) $$('chk_ong').attachEvent('onChange', chk_ong_change)
$$('cmd_subir_certificado').attachEvent('onItemClick', cmd_subir_certificado_click)
$$('up_cert').attachEvent('onUploadComplete', up_cert_upload_complete)
} }
} }
@ -141,11 +142,29 @@ function get_emisor(){
} }
function get_certificado(){
var form = $$('form_cert')
webix.ajax().get("/values/cert", {}, {
error: function(text, data, xhr) {
msg = 'Error al consultar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json()
form.setValues(values)
}
})
}
function multi_admin_change(prevID, nextID){ function multi_admin_change(prevID, nextID){
//~ webix.message(nextID) //~ webix.message(nextID)
if(nextID == 'app_emisor'){ if(nextID == 'app_emisor'){
$$('tab_emisor').setValue('Datos Fiscales') $$('tab_emisor').setValue('Datos Fiscales')
get_emisor() get_emisor()
get_certificado()
return return
} }
@ -221,3 +240,99 @@ function chk_ong_change(new_value, old_value){
$$('ong_fecha_dof').disable() $$('ong_fecha_dof').disable()
} }
} }
function cmd_subir_certificado_click(){
var form = $$('form_upload')
if (!form.validate()){
msg = 'Valores inválidos'
msg_error(msg)
return
}
var values = form.getValues()
if(!values.contra.trim()){
msg = 'La contraseña no puede estar vacía'
msg_error(msg)
return
}
if($$('lst_cert').count() < 2){
msg = 'Selecciona al menos dos archivos: CER y KEY del certificado.'
msg_error(msg)
return
}
if($$('lst_cert').count() > 2){
msg = 'Selecciona solo dos archivos: CER y KEY del certificado.'
msg_error(msg)
return
}
var fo1 = $$('up_cert').files.getItem($$('up_cert').files.getFirstId())
var fo2 = $$('up_cert').files.getItem($$('up_cert').files.getLastId())
var ext = ['key', 'cer']
if(ext.indexOf(fo1.type.toLowerCase()) == -1 || ext.indexOf(fo2.type.toLowerCase()) == -1){
msg = 'Archivos inválidos, se requiere un archivo CER y un KEY.'
msg_error(msg)
return
}
if(fo1.type == fo2.type && fo1.size == fo2.size){
msg = 'Selecciona archivos diferentes: un archivo CER y un KEY.'
msg_error(msg)
return
}
var rfc = $$('form_cert').getValues()['cert_rfc']
if(rfc){
msg = 'Ya existe un certificado guardado<BR><BR>¿Deseas reemplazarlo?'
webix.confirm({
title: 'Certificado Existente',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
$$('up_cert').send()
}
}
})
}
}
function up_cert_upload_complete(response){
if(response.status != 'server'){
msg = 'Ocurrio un error al subir los archivos'
msg_error(msg)
return
}
msg = 'Archivos subidos correctamente. Esperando validación'
msg_sucess(msg)
var values = $$('form_upload').getValues()
$$('form_upload').setValues({})
$$('up_cert').files.data.clearAll()
webix.ajax().post('/values/cert', values, {
error:function(text, data, XmlHttpRequest){
msg = 'Ocurrio un error, consulta a soporte técnico'
msg_error(msg)
},
success:function(text, data, XmlHttpRequest){
var values = data.json()
if(values.ok){
$$('form_cert').setValues(values.data)
msg_sucess(values.msg)
}else{
msg_error(values.msg)
}
}
})
}

View File

@ -104,15 +104,15 @@ var emisor_certificado = [
{template: 'Cargar Certificado', type: 'section'}, {template: 'Cargar Certificado', type: 'section'},
{view: 'form', id: 'form_upload', rows: [ {view: 'form', id: 'form_upload', rows: [
{cols: [{}, {cols: [{},
{view: 'uploader', id: 'up_cert', autosend:false, link: 'lst_cert', {view: 'uploader', id: 'up_cert', autosend: false, link: 'lst_cert',
value: 'Seleccionar certificado', upload: '/values/cert'}, {}]}, value: 'Seleccionar certificado', upload: '/values/files'}, {}]},
{cols: [{}, {cols: [{},
{view: 'list', id: 'lst_cert', type: 'uploader', autoheight:true, {view: 'list', id: 'lst_cert', name: 'certificado',
borderless: true}, {}]}, type: 'uploader', autoheight:true, borderless: true}, {}]},
{cols: [{}, {cols: [{},
{view: 'text', id: 'txt_contra', label: 'Contraseña KEY', {view: 'text', id: 'txt_contra', name: 'contra',
labelPosition: 'top', labelAlign: 'center', type: 'password', label: 'Contraseña KEY', labelPosition: 'top',
required: true}, {}]}, labelAlign: 'center', type: 'password', required: true}, {}]},
{cols: [{}, {view: 'button', id: 'cmd_subir_certificado', {cols: [{}, {view: 'button', id: 'cmd_subir_certificado',
label: 'Subir certificado'}, {}]}, label: 'Subir certificado'}, {}]},
]}, ]},