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-multipart
Beaker
Mako
peewee
click
logbook
bcrypt
python-dateutil

View File

@ -70,6 +70,17 @@ class AppValues(object):
req.context['result'] = self._db.get_values(table, values)
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):

View File

@ -2,17 +2,20 @@
import datetime
import getpass
import hashlib
import json
import mimetypes
import os
import re
import sqlite3
import subprocess
import tempfile
import unicodedata
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):
@ -23,6 +26,17 @@ from settings import log, template_lookup, COMPANIES, DB_SAT
#~ 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():
password = getpass.getpass('Introduce la contraseña: ')
pass2 = getpass.getpass('Confirma la contraseña: ')
@ -177,3 +191,148 @@ def to_slug(string):
.encode('ascii', 'ignore')
.decode('ascii').lower())
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
import falcon
from falcon_multipart.middleware import MultipartMiddleware
from beaker.middleware import SessionMiddleware
from middleware import (
@ -20,8 +21,12 @@ from settings import DEBUG
db = StorageEngine()
api = falcon.API(
middleware=[AuthMiddleware(), JSONTranslator(), ConnectionMiddleware()])
api = falcon.API(middleware=[
AuthMiddleware(),
JSONTranslator(),
ConnectionMiddleware(),
MultipartMiddleware(),
])
api.req_options.auto_parse_form_urlencoded = True
api.add_sink(handle_404, '')

View File

@ -14,6 +14,15 @@ class StorageEngine(object):
def get_values(self, table, values=None):
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):
return main.get_cp(values['cp'])

View File

@ -238,20 +238,77 @@ class Emisor(BaseModel):
class Certificado(BaseModel):
key = BlobField()
key = BlobField(null=True)
key_tmp = BlobField(null=True)
key_enc = TextField(default='')
cer = BlobField()
cer = BlobField(null=True)
cer_tmp = BlobField(null=True)
cer_pem = TextField(default='')
cer_txt = TextField(default='')
p12 = BlobField()
p12 = BlobField(null=True)
serie = TextField(default='')
rfc = TextField(default='')
desde = DateTimeField()
hasta = DateTimeField()
desde = DateTimeField(null=True)
hasta = DateTimeField(null=True)
def __str__(self):
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):
serie = TextField(unique=True)

View File

@ -13,7 +13,8 @@ var controllers = {
$$('emisor_cp').attachEvent('onTimedKeyPress', emisor_postal_code_key_up)
$$('chk_escuela').attachEvent('onChange', chk_escuela_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){
//~ webix.message(nextID)
if(nextID == 'app_emisor'){
$$('tab_emisor').setValue('Datos Fiscales')
get_emisor()
get_certificado()
return
}
@ -221,3 +240,99 @@ function chk_ong_change(new_value, old_value){
$$('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'},
{view: 'form', id: 'form_upload', rows: [
{cols: [{},
{view: 'uploader', id: 'up_cert', autosend:false, link: 'lst_cert',
value: 'Seleccionar certificado', upload: '/values/cert'}, {}]},
{view: 'uploader', id: 'up_cert', autosend: false, link: 'lst_cert',
value: 'Seleccionar certificado', upload: '/values/files'}, {}]},
{cols: [{},
{view: 'list', id: 'lst_cert', type: 'uploader', autoheight:true,
borderless: true}, {}]},
{view: 'list', id: 'lst_cert', name: 'certificado',
type: 'uploader', autoheight:true, borderless: true}, {}]},
{cols: [{},
{view: 'text', id: 'txt_contra', label: 'Contraseña KEY',
labelPosition: 'top', labelAlign: 'center', type: 'password',
required: true}, {}]},
{view: 'text', id: 'txt_contra', name: 'contra',
label: 'Contraseña KEY', labelPosition: 'top',
labelAlign: 'center', type: 'password', required: true}, {}]},
{cols: [{}, {view: 'button', id: 'cmd_subir_certificado',
label: 'Subir certificado'}, {}]},
]},