1283 lines
No EOL
30 KiB
JavaScript
1283 lines
No EOL
30 KiB
JavaScript
|
|
function HermesMsg(baseUrl)
|
|
{
|
|
// Estados del servicio
|
|
|
|
this.NoRegistrado = 0;
|
|
this.Registrando = 1;
|
|
this.Registrado = 2;
|
|
this.Respondiendo = 3;
|
|
|
|
// Estados del cliente
|
|
|
|
this.Desocupado = 0;
|
|
this.EnviandoMensaje = 1;
|
|
this.EsperandoRespuesta = 2;
|
|
|
|
// Tipos de mensajes
|
|
|
|
this.MensajeCliente = 0;
|
|
this.MensajeInicioSesion = 1;
|
|
this.MensajeEstablecerSesion = 2;
|
|
|
|
// Dirección base para los URLs que utiliza esta clase
|
|
|
|
this.baseUrl = baseUrl;
|
|
|
|
// Indica si hay un llamado ajax en curso
|
|
|
|
this.esperandoRespuesta = false;
|
|
|
|
// Identificador asignado a este servicio
|
|
|
|
this.servicioId = null;
|
|
|
|
// Estado del servicio
|
|
|
|
this.Estado = this.NoRegistrado;
|
|
|
|
// Estado del cliente
|
|
|
|
this.EstadoMensaje = this.Desocupado;
|
|
|
|
// Id del ultimo mensaje enviado
|
|
|
|
this.MensajeId = null;
|
|
|
|
// lista de mensajes
|
|
|
|
this.Mensajes = new Array();
|
|
|
|
// callback que se llama cuando hay un mensaje listo
|
|
|
|
this.MensajeListo = function () { };
|
|
|
|
// Este evento se llama cuando se detecta que el servicio se detuvo
|
|
|
|
this.ServicioDetenido = function () { };
|
|
|
|
// Este campo tiene la información de la sesión o null si no hay una sesion segura
|
|
|
|
this.sesion = null;
|
|
|
|
// Objeto que contiene la funcionalidad de encripción
|
|
|
|
this.subtle = null;
|
|
|
|
if (window.msrCrypto)
|
|
{
|
|
this.subtle = window.msrCrypto.subtle;
|
|
}
|
|
|
|
// Especifica el tipo de mensaje que se va a enviar
|
|
|
|
this.TipoMensaje = this.MensajeCliente;
|
|
|
|
// Contiene las llaves RSA del servicio
|
|
|
|
this.privateKey = null;
|
|
|
|
this.publicKeyJwk = null;
|
|
|
|
// Contiene las llaves de encripción de las sesiones
|
|
|
|
this.sesiones = new Array();
|
|
|
|
// Controla si se aceptan o no mensajes sin encripción
|
|
|
|
this.encryptionRequired = false;
|
|
|
|
}
|
|
|
|
HermesMsg.prototype.RegistrarServicio =
|
|
function (callback, callbackError)
|
|
{
|
|
if (this.Estado != this.NoRegistrado)
|
|
{
|
|
safeCallback(callbackError, "No está registrado");
|
|
return false;
|
|
}
|
|
|
|
this.Estado = this.Registrando;
|
|
|
|
var obj = this;
|
|
|
|
this.HacerSolicitud(
|
|
this.CreateUrl("servicio/CrearServicio"),
|
|
function (respuesta)
|
|
{
|
|
if (respuesta.Resultado)
|
|
{
|
|
obj.servicioId = respuesta.Datos;
|
|
obj.Estado = obj.Registrado;
|
|
|
|
safeCallback(callback);
|
|
|
|
// Iniciar timer para revisar mensajes para el servicio
|
|
|
|
obj.PonerTimeoutRevisionMensajesPendientes();
|
|
|
|
// Crear las llaves RSA y exportar la llave publica
|
|
|
|
var crypto = window.crypto || window.msCrypto;
|
|
var subtle = crypto.subtle || crypto.webkitSubtle;
|
|
|
|
var keySetupFailedCallback =
|
|
function()
|
|
{
|
|
obj.privateKey = null;
|
|
obj.publicKeyJwk = null;
|
|
}
|
|
|
|
if (crypto != null) {
|
|
|
|
var result = subtle.generateKey(
|
|
{
|
|
name: "RSA-OAEP",
|
|
modulusLength: 2048, //can be 1024, 2048, or 4096
|
|
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
|
|
hash: { name: "SHA-256" }
|
|
},
|
|
true, //whether the key is extractable (i.e. can be used in exportKey)
|
|
["encrypt", "decrypt"] //must contain both "encrypt" and "decrypt"
|
|
);
|
|
|
|
CryptoCall(
|
|
result,
|
|
function (rsaKeys) {
|
|
|
|
// Exportar la llave publica
|
|
|
|
result = subtle.exportKey(
|
|
"jwk",
|
|
rsaKeys.publicKey
|
|
);
|
|
|
|
CryptoCall(
|
|
result,
|
|
function (publicKeyJwk) {
|
|
|
|
// Convertir publicKeyJwk a string y guardarlo en el campo del mismo nombre. Si es necesario convertirlo a JSON (IE)
|
|
|
|
if (GetType(publicKeyJwk) != "ArrayBuffer") {
|
|
obj.publicKeyJwk = JSON.stringify(publicKeyJwk);
|
|
}
|
|
else {
|
|
|
|
// IE retorna un ArrayBuffer
|
|
|
|
var s = "";
|
|
var bytes = new Int8Array(publicKeyJwk);
|
|
for (var i = 0; i < bytes.length; i++) {
|
|
if (bytes[i] == 0) //IE agrega un 0 al final
|
|
{
|
|
continue;
|
|
}
|
|
s += String.fromCharCode(bytes[i]);
|
|
}
|
|
obj.publicKeyJwk = s;
|
|
}
|
|
|
|
// Exportar la llave privada
|
|
|
|
result = subtle.exportKey(
|
|
"jwk",
|
|
rsaKeys.privateKey
|
|
);
|
|
|
|
CryptoCall(
|
|
result,
|
|
function (privateKeyJwk) {
|
|
|
|
// IE retorna un ArrayBuffer
|
|
|
|
if (GetType(privateKeyJwk) != "ArrayBuffer") {
|
|
privateKeyJwk = JSON.stringify(privateKeyJwk);
|
|
}
|
|
|
|
result = obj.subtle.importKey(
|
|
"jwk",
|
|
toSupportedArray(privateKeyJwk),
|
|
{ name: "RSAES-PKCS1-v1_5" },
|
|
true,
|
|
["decrypt"]
|
|
);
|
|
|
|
CryptoCall(
|
|
result,
|
|
function (privateKey) {
|
|
obj.privateKey = privateKey;
|
|
},
|
|
keySetupFailedCallback
|
|
);
|
|
},
|
|
keySetupFailedCallback
|
|
);
|
|
},
|
|
keySetupFailedCallback
|
|
);
|
|
},
|
|
keySetupFailedCallback
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
obj.servicioId = null;
|
|
obj.Estado = obj.NoRegistrado;
|
|
safeCallback(callbackError, respuesta.Error);
|
|
}
|
|
},
|
|
function (error)
|
|
{
|
|
obj.Estado = obj.NoRegistrado;
|
|
safeCallback(callbackError,error);
|
|
}
|
|
);
|
|
return true;
|
|
}
|
|
|
|
HermesMsg.prototype.HacerSolicitud =
|
|
function (url, callback, errorCallback, mensaje)
|
|
{
|
|
|
|
this.esperandoRespuesta = true;
|
|
|
|
var obj = this;
|
|
|
|
// Convertir el objeto mensaje en una hilera en formato form-data
|
|
|
|
var postData = "";
|
|
if (mensaje != null)
|
|
{
|
|
for(var key in mensaje)
|
|
{
|
|
postData += key + "=" + encodeURIComponent(mensaje[key]) + "&";
|
|
}
|
|
if (postData.length>0 && postData[postData.length-1] == "&")
|
|
{
|
|
postData = postData.substr(0, postData.length - 1);
|
|
}
|
|
}
|
|
|
|
var result =
|
|
$.ajax(
|
|
{
|
|
type: "POST",
|
|
url: url,
|
|
data: postData,
|
|
dataType: "json",
|
|
async: true,
|
|
cache: false,
|
|
error:
|
|
function (error)
|
|
{
|
|
obj.esperandoRespuesta = false;
|
|
|
|
safeCallback(errorCallback,error);
|
|
},
|
|
success:
|
|
function (respuesta)
|
|
{
|
|
obj.esperandoRespuesta = false;
|
|
|
|
safeCallback(callback,respuesta);
|
|
}
|
|
}
|
|
);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
HermesMsg.prototype.ServicioId =
|
|
function ()
|
|
{
|
|
return this.servicioId;
|
|
}
|
|
|
|
HermesMsg.prototype.CreateUrl =
|
|
function (urlPart) {
|
|
var url = this.baseUrl;
|
|
if (url.length == 0 || url[url.length - 1] != '/') {
|
|
url += "/";
|
|
}
|
|
url += urlPart;
|
|
return url;
|
|
}
|
|
|
|
HermesMsg.prototype.EnviarMensaje =
|
|
function(servicioId, contenido, callback, callbackError)
|
|
{
|
|
if (this.EstadoMensaje != this.Desocupado)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
this.EstadoMensaje = this.EnviandoMensaje;
|
|
|
|
var obj = this;
|
|
|
|
var mensaje = { contenido: contenido, tipo: this.TipoMensaje };
|
|
|
|
// Si se establecio una sesión hay que encriptar el mensaje
|
|
|
|
if (this.sesion != null && this.sesion.idSesion != null)
|
|
{
|
|
// Encriptar el mensaje
|
|
|
|
var result = obj.subtle.encrypt(
|
|
{ name: "AES-CBC", iv: this.sesion.iv },
|
|
this.sesion.aesKey,
|
|
toSupportedArray(JSON.stringify(contenido))
|
|
);
|
|
|
|
CryptoCall(
|
|
result,
|
|
function (cipherText) {
|
|
|
|
mensaje = { contenido: _arrayBufferToBase64(cipherText), tipo: obj.TipoMensaje, sesion: obj.sesion.idSesion };
|
|
|
|
obj.EnviarMensajeInterno(servicioId, mensaje, callback, callbackError);
|
|
},
|
|
function (error)
|
|
{
|
|
obj.Estado = obj.Desocupado;
|
|
safeCallback(callbackError, error);
|
|
}
|
|
|
|
);
|
|
}
|
|
else
|
|
{
|
|
this.EnviarMensajeInterno(servicioId, mensaje, callback, callbackError);
|
|
}
|
|
}
|
|
|
|
HermesMsg.prototype.EnviarMensajeInterno =
|
|
function (servicioId, mensaje, callback, callbackError)
|
|
{
|
|
var obj = this;
|
|
|
|
var result = this.HacerSolicitud(
|
|
this.CreateUrl("servicio/EnviarMensaje"),
|
|
function (respuesta) {
|
|
if (respuesta.Resultado) {
|
|
|
|
obj.EstadoMensaje = obj.EsperandoRespuesta;
|
|
|
|
var mensajeId = respuesta.Datos.Id;
|
|
|
|
// Monitorear constantemente para ver si ya está lista la respuesta
|
|
|
|
window.setTimeout(
|
|
function () {
|
|
obj.RevisarRespuesta(servicioId, mensajeId, callback, callbackError);
|
|
},
|
|
1000
|
|
);
|
|
}
|
|
else {
|
|
|
|
deleteFromCache("Sesion");
|
|
|
|
obj.EstadoMensaje = obj.Desocupado;
|
|
|
|
safeCallback(callbackError, respuesta.Error);
|
|
}
|
|
},
|
|
function (error) {
|
|
|
|
deleteFromCache("Sesion");
|
|
|
|
obj.Estado = obj.Desocupado;
|
|
|
|
safeCallback(callbackError, error);
|
|
},
|
|
{ servicioId: servicioId, contenido: JSON.stringify(mensaje) }
|
|
);
|
|
}
|
|
|
|
HermesMsg.prototype.RevisarRespuesta =
|
|
function (servicioId, mensajeId, callback, callbackError)
|
|
{
|
|
var obj = this;
|
|
var result = this.HacerSolicitud(
|
|
this.CreateUrl("servicio/RecibirRespuesta"),
|
|
function (respuesta) {
|
|
if (respuesta.Resultado) {
|
|
|
|
if(respuesta.Datos != null)
|
|
{
|
|
// Esto significa que la respuesta del mensaje esta lista
|
|
|
|
window.clearInterval(obj.intervalId);
|
|
|
|
obj.EstadoMensaje = obj.Desocupado;
|
|
|
|
// Si han una sesión entonces se debe desencriptar el mensaje
|
|
|
|
if (obj.sesion != null && obj.sesion.idSesion != null) {
|
|
|
|
// Desencriptar el mensaje
|
|
|
|
var result = obj.subtle.decrypt(
|
|
{ name: "AES-CBC", iv: toSupportedArray(obj.sesion.iv) },
|
|
obj.sesion.aesKey,
|
|
toSupportedArray(_base64ToArrayBuffer(respuesta.Datos))
|
|
);
|
|
|
|
CryptoCall(
|
|
result,
|
|
function (plainText)
|
|
{
|
|
var s = "";
|
|
var bytes = new Int8Array(plainText);
|
|
for (var i = 0; i < bytes.length; i++) {
|
|
if (bytes[i] == 0) //IE agrega un 0 al final
|
|
{
|
|
continue;
|
|
}
|
|
s += String.fromCharCode(bytes[i]);
|
|
}
|
|
|
|
var mensaje = JSON.parse(s);
|
|
|
|
safeCallback(callback, mensaje.contenido);
|
|
},
|
|
function ()
|
|
{
|
|
safeCallback(callbackError);
|
|
}
|
|
);
|
|
}
|
|
else {
|
|
var mensaje = JSON.parse(respuesta.Datos);
|
|
|
|
safeCallback(callback, mensaje.contenido);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// La respuesta no está lista, volver a ejecutar timeout
|
|
|
|
window.setTimeout(
|
|
function () {
|
|
obj.RevisarRespuesta(servicioId, mensajeId, callback, callbackError);
|
|
},
|
|
1000
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
|
|
// Esto significa que el mensaje no existe
|
|
|
|
deleteFromCache("Sesion");
|
|
|
|
obj.EstadoMensaje = obj.Desocupado;
|
|
|
|
safeCallback(callbackError);
|
|
}
|
|
},
|
|
function (error) {
|
|
|
|
// Esto es un error técnico, por lo que no se cancela el timer
|
|
|
|
window.setTimeout(
|
|
function () {
|
|
obj.RevisarRespuesta(servicioId, mensajeId, callback, callbackError);
|
|
},
|
|
1000
|
|
);
|
|
},
|
|
{servicioId : servicioId, mensajeId : mensajeId}
|
|
);
|
|
|
|
}
|
|
|
|
|
|
HermesMsg.prototype.ProcesarMensajesPendientes =
|
|
function ()
|
|
{
|
|
var obj = this;
|
|
var result = this.HacerSolicitud(
|
|
this.CreateUrl("servicio/MensajesPendientes"),
|
|
function (respuesta) {
|
|
|
|
obj.PonerTimeoutRevisionMensajesPendientes();
|
|
|
|
if (respuesta.Resultado) {
|
|
|
|
// Procesar los mensajes recibidos
|
|
|
|
var mensajesCliente = new Array();
|
|
|
|
for (var i = 0; i < respuesta.Datos.length; i++)
|
|
{
|
|
respuesta.Datos[i]._Respuesta = null; // Significa que el mensaje todavia no tiene una respuesta
|
|
|
|
var mensaje = JSON.parse(respuesta.Datos[i].Mensaje);
|
|
|
|
if (mensaje.tipo == obj.MensajeInicioSesion) {
|
|
|
|
var mensajeActual = respuesta.Datos[i];
|
|
|
|
// Se debe responder con la llave publica del servicio
|
|
|
|
var mensaje2 = { contenido: obj.publicKeyJwk };
|
|
|
|
// Enviar la respuesta del mensaje
|
|
|
|
obj.HacerSolicitud(
|
|
obj.CreateUrl("servicio/EnviarRespuesta"),
|
|
function (respuesta) {
|
|
|
|
// No hay nada que hacer, el mensaje sale de la lista de pendientes
|
|
|
|
},
|
|
function (error) {
|
|
// En este caso no se responde y el cliente debe reintentar
|
|
},
|
|
{ servicioId: obj.servicioId, mensajeId: mensajeActual.Id, contenido: JSON.stringify(mensaje2) }
|
|
);
|
|
|
|
}
|
|
else if (mensaje.tipo == obj.MensajeEstablecerSesion)
|
|
{
|
|
|
|
var mensajeActual = respuesta.Datos[i];
|
|
|
|
// Desencriptar la llave AES y el vector iv y guardarla en Sesiones
|
|
|
|
var encryptedKey = _base64ToArrayBuffer(mensaje.contenido);
|
|
|
|
// Si se produce un error se retorna -1 al cliente
|
|
|
|
var errorCallback =
|
|
function ()
|
|
{
|
|
obj.HacerSolicitud(
|
|
obj.CreateUrl("servicio/EnviarRespuesta"),
|
|
function (respuesta) {},
|
|
function (error) {},
|
|
{ servicioId: obj.servicioId, mensajeId: mensajeActual.Id, contenido: JSON.stringify({contenido:-1}) }
|
|
);
|
|
};
|
|
try
|
|
{
|
|
var result = obj.subtle.decrypt(
|
|
{ name: "RSAES-PKCS1-v1_5" },
|
|
obj.privateKey,
|
|
toSupportedArray(encryptedKey)
|
|
);
|
|
|
|
CryptoCall(
|
|
result,
|
|
function (aesKeyIvBytes) {
|
|
|
|
// Separar la llave AES y el vector IV
|
|
|
|
var aesKeyIvBytes8 = new Uint8Array(aesKeyIvBytes);
|
|
|
|
var aesKeyBytes = new Uint8Array(aesKeyIvBytes8.length - 16);
|
|
|
|
var iv = new Uint8Array(16);
|
|
|
|
for (var i = 0; i < aesKeyBytes.length; i++)
|
|
{
|
|
aesKeyBytes[i] = aesKeyIvBytes8[i];
|
|
}
|
|
|
|
for (var i = 0; i < iv.length; i++)
|
|
{
|
|
iv[i] = aesKeyIvBytes8[i + aesKeyBytes.length];
|
|
}
|
|
|
|
// Importar la llave AES
|
|
|
|
var result = obj.subtle.importKey(
|
|
"raw",
|
|
toSupportedArray(aesKeyBytes),
|
|
{
|
|
name: "AES-CBC",
|
|
length: 256, //can be 128, 192, or 256
|
|
},
|
|
true,
|
|
["encrypt", "decrypt"]
|
|
);
|
|
|
|
CryptoCall(
|
|
result,
|
|
function (aesKey) {
|
|
|
|
// Incluir la llave AES y el vector IV en la lista de sesiones y retornar
|
|
// el numero de sesion al cliente
|
|
|
|
obj.sesiones.push({ aesKey: aesKey, iv: iv });
|
|
|
|
obj.HacerSolicitud(
|
|
obj.CreateUrl("servicio/EnviarRespuesta"),
|
|
function (respuesta) {},
|
|
function (error) { },
|
|
{ servicioId: obj.servicioId, mensajeId: mensajeActual.Id, contenido: JSON.stringify({ contenido: obj.sesiones.length }) }
|
|
);
|
|
|
|
},
|
|
errorCallback
|
|
);
|
|
},
|
|
errorCallback
|
|
);
|
|
}
|
|
catch(e)
|
|
{
|
|
errorCallback();
|
|
}
|
|
}
|
|
else if (mensaje.tipo == obj.MensajeCliente) {
|
|
|
|
var mensajeActual = respuesta.Datos[i];
|
|
|
|
// Si el mensaje está encriptado es necesario desencriptarlo
|
|
|
|
if (mensaje.sesion != null) {
|
|
|
|
// esto permite identificar que la respuesta del mensaje debe encriptarse
|
|
|
|
mensajeActual.sesion = mensaje.sesion;
|
|
|
|
// Desencriptar el mensaje
|
|
|
|
var iv = obj.sesiones[mensaje.sesion-1].iv;
|
|
var aesKey = obj.sesiones[mensaje.sesion-1].aesKey;
|
|
|
|
var result = obj.subtle.decrypt(
|
|
{ name: "AES-CBC", iv: toSupportedArray(iv) },
|
|
aesKey,
|
|
toSupportedArray(_base64ToArrayBuffer(mensaje.contenido))
|
|
);
|
|
|
|
CryptoCall(
|
|
result,
|
|
function (plainText)
|
|
{
|
|
// Convertir los bytes a una hilera
|
|
|
|
var s = "";
|
|
var bytes = new Int8Array(plainText);
|
|
for (var i = 0; i < bytes.length; i++) {
|
|
if (bytes[i] == 0) //IE agrega un 0 al final
|
|
{
|
|
continue;
|
|
}
|
|
s += String.fromCharCode(bytes[i]);
|
|
}
|
|
|
|
mensajeActual.Mensaje = JSON.parse(s);
|
|
|
|
obj.Mensajes.push(mensajeActual);
|
|
|
|
try {
|
|
obj.MensajeListo(mensajeActual);
|
|
}
|
|
catch (e) {
|
|
}
|
|
},
|
|
function () {}
|
|
);
|
|
}
|
|
else {
|
|
|
|
// Si se requiere encripción entonces se debe rechazar el mensaje sin encriptar
|
|
|
|
if (obj.encryptionRequired)
|
|
{
|
|
mensajeActual._Respuesta = "Mensaje rechazado porque no está encriptado";
|
|
obj.Mensajes.push(mensajeActual);
|
|
}
|
|
else
|
|
{
|
|
mensajeActual.Mensaje = mensaje.contenido;
|
|
|
|
obj.Mensajes.push(mensajeActual);
|
|
|
|
mensajesCliente.push(mensajeActual);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Notificar que se recibieron nuevos mensajes
|
|
|
|
if (obj.MensajeListo != null && mensajesCliente.length > 0)
|
|
{
|
|
try
|
|
{
|
|
obj.MensajeListo(mensajesCliente[0]);
|
|
}
|
|
catch(e)
|
|
{
|
|
}
|
|
}
|
|
|
|
// Enviar las respuestas pendientes de enviar
|
|
|
|
if (obj.Mensajes.length > 0)
|
|
{
|
|
obj.EnviarRespuestasPendientes();
|
|
}
|
|
|
|
}
|
|
else {
|
|
|
|
// Esto significa que el servicio esta inactivo o no existe
|
|
|
|
obj.Estado = obj.NoRegistrado;
|
|
|
|
safeCallback(obj.ServicioDetenido);
|
|
}
|
|
},
|
|
function (error) {
|
|
|
|
// Esto es un error técnico, por lo que no se cancela el timer
|
|
|
|
obj.PonerTimeoutRevisionMensajesPendientes();
|
|
|
|
},
|
|
{ servicioId: this.servicioId }
|
|
);
|
|
}
|
|
|
|
HermesMsg.prototype.ResponderMensaje =
|
|
function(mensaje, respuesta)
|
|
{
|
|
// Asegurarse que el servicio esté registrado
|
|
|
|
if (this.Estado != this.Registrado) {
|
|
|
|
safeCallback(callbackError, "Estado invalido");
|
|
|
|
return false;
|
|
}
|
|
|
|
mensaje._Respuesta = respuesta;
|
|
|
|
this.EnviarRespuestasPendientes();
|
|
}
|
|
|
|
HermesMsg.prototype.EnviarRespuestasPendientes =
|
|
function()
|
|
{
|
|
// Si estamos en medio de una solicitud Ajax entonces esperar
|
|
|
|
if (this.esperandoRespuesta)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Buscar el primer mensaje pendiente
|
|
|
|
var pendientes = new Array(); // al final del ciclo contendrá los mensajes pendientes
|
|
|
|
var mensaje = null; // al final del ciclo tendrá el mensaje que se va a responder
|
|
|
|
var obj = this;
|
|
|
|
for (var i = 0; i < this.Mensajes.length; i++ )
|
|
{
|
|
|
|
// Si el mensaje actual no tiene una respuesta entonces
|
|
// introducirlo en la lista de pendientes
|
|
|
|
if (this.Mensajes[i]._Respuesta == null || mensaje != null)
|
|
{
|
|
pendientes.push(this.Mensajes[i]);
|
|
continue;
|
|
}
|
|
|
|
mensaje = this.Mensajes[i];
|
|
}
|
|
|
|
// Enviar el mensaje pendiente que se encontro
|
|
|
|
if (mensaje != null)
|
|
{
|
|
this.Mensajes = pendientes;
|
|
|
|
var mensaje2 = { contenido: mensaje._Respuesta };
|
|
|
|
// Si hay una sesión es necesaro encriptar la respuesta
|
|
|
|
if (mensaje.sesion != null) {
|
|
|
|
var iv = obj.sesiones[mensaje.sesion - 1].iv;
|
|
var aesKey = obj.sesiones[mensaje.sesion - 1].aesKey;
|
|
|
|
var result = obj.subtle.encrypt(
|
|
{ name: "AES-CBC", iv: toSupportedArray(iv) },
|
|
aesKey,
|
|
toSupportedArray(JSON.stringify(mensaje2))
|
|
);
|
|
|
|
CryptoCall(
|
|
result,
|
|
function (encrypted)
|
|
{
|
|
// Enviar la respuesta del mensaje
|
|
obj.HacerSolicitud(
|
|
obj.CreateUrl("servicio/EnviarRespuesta"),
|
|
function (respuesta) {
|
|
|
|
// No hay nada que hacer, el mensaje sale de la lista de pendientes
|
|
|
|
},
|
|
function (error) {
|
|
|
|
// Si hay un error tecnico el mensaje vuelve a la lista de pendientes
|
|
|
|
obj.Mensajes.push(mensaje);
|
|
},
|
|
{ servicioId: obj.servicioId, mensajeId: mensaje.Id, contenido: _arrayBufferToBase64(encrypted) }
|
|
);
|
|
},
|
|
function (error) {
|
|
|
|
// Si hay un error tecnico el mensaje vuelve a la lista de pendientes
|
|
|
|
obj.Mensajes.push(mensaje);
|
|
}
|
|
);
|
|
}
|
|
else {
|
|
|
|
// Enviar la respuesta del mensaje
|
|
|
|
this.HacerSolicitud(
|
|
this.CreateUrl("servicio/EnviarRespuesta"),
|
|
function (respuesta) {
|
|
|
|
// No hay nada que hacer, el mensaje sale de la lista de pendientes
|
|
|
|
},
|
|
function (error) {
|
|
|
|
// Si hay un error tecnico el mensaje vuelve a la lista de pendientes
|
|
|
|
obj.Mensajes.push(mensaje);
|
|
},
|
|
{ servicioId: this.servicioId, mensajeId: mensaje.Id, contenido: JSON.stringify(mensaje2) }
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
HermesMsg.prototype.DetenerServicio =
|
|
function (callback, callbackError) {
|
|
|
|
// No se puede detener si no está registrado
|
|
|
|
if (this.Estado != this.Registrado) {
|
|
|
|
safeCallback(callbackError,"Estado invalido");
|
|
|
|
return false;
|
|
}
|
|
|
|
var obj = this;
|
|
|
|
this.HacerSolicitud(
|
|
this.CreateUrl("servicio/DetenerServicio"),
|
|
function (respuesta) {
|
|
obj.servicioId = null;
|
|
obj.Estado = obj.NoRegistrado;
|
|
safeCallback(callback);
|
|
},
|
|
function (error) {
|
|
safeCallback(callbackError,error);
|
|
},
|
|
{ servicioId: this.servicioId }
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
HermesMsg.prototype.IniciarSesion =
|
|
function (servicioId, callback, callbackError) {
|
|
|
|
// Verificar que se haya incluido la libreria msrCrypto
|
|
|
|
if (!this.subtle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (this.sesion != null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var obj = this;
|
|
|
|
// Tratar de recuperar la sesion del cache
|
|
|
|
var sesion = getFromCache("Sesion");
|
|
|
|
if (sesion != null)
|
|
{
|
|
var keyData8 = new Uint8Array(_base64ToArrayBuffer(sesion.keyData));
|
|
|
|
var result = obj.subtle.importKey(
|
|
"raw",
|
|
toSupportedArray(keyData8),
|
|
{
|
|
name: "AES-CBC",
|
|
length: 256, //can be 128, 192, or 256
|
|
},
|
|
true,
|
|
["encrypt", "decrypt"]
|
|
);
|
|
|
|
CryptoCall(
|
|
result,
|
|
function (aesKey) {
|
|
|
|
obj.sesion = sesion.sesion;
|
|
|
|
obj.sesion.aesKey = aesKey;
|
|
|
|
obj.sesion.iv = new Uint8Array(_base64ToArrayBuffer(sesion.iv));
|
|
|
|
safeCallback(callback);
|
|
|
|
},
|
|
callbackError
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Solicitar al servicio que inicie una sesión segura
|
|
|
|
this.TipoMensaje = this.MensajeInicioSesion;
|
|
|
|
// Si se produce un error este callback pone el campo sesion en null
|
|
// para establecer que no hay una sesión
|
|
|
|
var callbackError2 =
|
|
function (error)
|
|
{
|
|
obj.TipoMensaje = obj.MensajeCliente;
|
|
obj.sesion = null;
|
|
safeCallback(callbackError, error);
|
|
}
|
|
|
|
this.EnviarMensaje(
|
|
servicioId,
|
|
"",
|
|
function(respuesta)
|
|
{
|
|
obj.TipoMensaje = obj.MensajeCliente;
|
|
|
|
// Si respuesta es vacio significa que hubo un error del
|
|
// lado del servidor
|
|
|
|
if (respuesta == null || respuesta == "")
|
|
{
|
|
callbackError2();
|
|
return;
|
|
}
|
|
|
|
obj.sesion = { publicKeyJwk: respuesta, idSesion:null };
|
|
|
|
// Importar la llave RSA recibida del servicio
|
|
|
|
var result = obj.subtle.importKey(
|
|
"jwk",
|
|
toSupportedArray(obj.sesion.publicKeyJwk),
|
|
{ name: "RSAES-PKCS1-v1_5" },
|
|
true,
|
|
["encrypt"]
|
|
);
|
|
|
|
CryptoCall(
|
|
result,
|
|
function (publicKey) {
|
|
|
|
// Generar la llave AES
|
|
|
|
var result = obj.subtle.generateKey(
|
|
{
|
|
name: "AES-CBC",
|
|
length: 256, //can be 128, 192, or 256
|
|
},
|
|
true, //whether the key is extractable (i.e. can be used in exportKey)
|
|
["encrypt", "decrypt"] //can be any combination of "encrypt" and "decrypt"
|
|
);
|
|
|
|
CryptoCall(
|
|
result,
|
|
function (aeskey) {
|
|
|
|
// Es necesario guardar la llave de encripción para encryptar los mensajes
|
|
// posteriores
|
|
|
|
obj.sesion.aesKey = aeskey;
|
|
|
|
// Exportar la llave AES para poder encriptarla luego con la llave publica del servicio
|
|
|
|
result = obj.subtle.exportKey(
|
|
"raw",
|
|
aeskey
|
|
);
|
|
|
|
CryptoCall(
|
|
result,
|
|
function (keyData) {
|
|
|
|
// Generar el vector IV para randomizar los paquetes encriptados
|
|
|
|
obj.sesion.iv = window.msrCrypto.getRandomValues(new Uint8Array(16));
|
|
|
|
// Crear un solo vector (textoPlano) que incluya la clave AES y el vector IV
|
|
// para su posterior encripción. El objetivo es hace una solo operación de encripción
|
|
// en lugar de dos, para disminuir el tiempo de ejecución
|
|
|
|
var keyData8 = new Uint8Array(keyData);
|
|
|
|
var textoPlano = new Uint8Array(keyData8.length + obj.sesion.iv.length);
|
|
|
|
for (i = 0; i < keyData8.length; i++)
|
|
{
|
|
textoPlano[i] = keyData8[i];
|
|
}
|
|
|
|
for (i = 0; i < obj.sesion.iv.length; i++)
|
|
{
|
|
textoPlano[keyData8.length+i] = obj.sesion.iv[i];
|
|
}
|
|
|
|
// Encriptar la llave AES + el vector IV
|
|
|
|
result = obj.subtle.encrypt(
|
|
{ name: "RSAES-PKCS1-v1_5" },
|
|
publicKey,
|
|
toSupportedArray(textoPlano)
|
|
);
|
|
|
|
CryptoCall(
|
|
result,
|
|
function (encryptedKeyData) {
|
|
|
|
// Enviar la llave AES y el IV encriptada al servicio
|
|
|
|
obj.TipoMensaje = obj.MensajeEstablecerSesion;
|
|
|
|
var ivAndAes =_arrayBufferToBase64(encryptedKeyData);
|
|
|
|
obj.EnviarMensaje(
|
|
servicioId,
|
|
ivAndAes,
|
|
function (respuesta) {
|
|
|
|
// Si respuesta es < 0 significa que se produjo un error
|
|
|
|
if (respuesta == null || respuesta < 0) {
|
|
callbackError2();
|
|
return;
|
|
}
|
|
|
|
obj.TipoMensaje = obj.MensajeCliente;
|
|
|
|
obj.sesion.idSesion = respuesta
|
|
|
|
addToCache(
|
|
"Sesion",
|
|
{ sesion: obj.sesion, keyData: _arrayBufferToBase64(keyData), iv: _arrayBufferToBase64(obj.sesion.iv.buffer) }
|
|
);
|
|
|
|
safeCallback(callback);
|
|
},
|
|
callbackError2
|
|
);
|
|
},
|
|
callbackError2
|
|
);
|
|
}
|
|
);
|
|
|
|
},
|
|
callbackError2
|
|
);
|
|
|
|
},
|
|
callbackError2
|
|
);
|
|
|
|
},
|
|
function()
|
|
{
|
|
obj.TipoMensaje = obj.MensajeCliente;
|
|
callbackError2();
|
|
}
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
HermesMsg.prototype.PonerTimeoutRevisionMensajesPendientes =
|
|
function () {
|
|
var obj = this;
|
|
window.setTimeout(
|
|
function () {
|
|
obj.ProcesarMensajesPendientes();
|
|
},
|
|
1000
|
|
);
|
|
}
|
|
|
|
function safeCallback(callback, param) {
|
|
if (callback == null) {
|
|
return;
|
|
}
|
|
try {
|
|
callback(param);
|
|
}
|
|
catch (e) {
|
|
}
|
|
}
|
|
|
|
function toSupportedArray(data) {
|
|
|
|
// does this browser support Typed Arrays?
|
|
var typedArraySupport = (typeof Uint8Array !== "undefined");
|
|
|
|
// get the data type of the parameter
|
|
var dataType = Object.prototype.toString.call(data);
|
|
dataType = dataType.substring(8, dataType.length - 1);
|
|
|
|
// determine the type
|
|
switch (dataType) {
|
|
|
|
// Regular JavaScript Array. Convert to Uint8Array if supported
|
|
// else do nothing and return the array
|
|
case "Array":
|
|
return typedArraySupport ? new Uint8Array(data) : data;
|
|
|
|
// ArrayBuffer. IE11 Web Crypto API returns ArrayBuffers that you have to convert
|
|
// to Typed Arrays. Convert to a Uint8Arrays and return;
|
|
case "ArrayBuffer":
|
|
return new Uint8Array(data);
|
|
|
|
// Already Uint8Array. Obviously there is support.
|
|
case "Uint8Array":
|
|
return data;
|
|
|
|
case "Uint16Array":
|
|
case "Uint32Array":
|
|
return new Uint8Array(data);
|
|
|
|
// String. Convert the string to a byte array using Typed Arrays if
|
|
// supported.
|
|
case "String":
|
|
var newArray = typedArraySupport ? new Uint8Array(data.length) : new Array(data.length);
|
|
for (var i = 0; i < data.length; i += 1) {
|
|
newArray[i] = data.charCodeAt(i);
|
|
}
|
|
return newArray;
|
|
|
|
// Some other type. Just return the data unchanged.
|
|
default:
|
|
throw new Error("toSupportedArray : unsupported data type " + dataType);
|
|
}
|
|
|
|
}
|
|
|
|
function CryptoCall(result, successCallback, errorCallback) {
|
|
if (result.then == null) {
|
|
result.oncomplete =
|
|
function () {
|
|
successCallback(result.result);
|
|
};
|
|
result.onerror =
|
|
function (err) {
|
|
errorCallback(err);
|
|
}
|
|
}
|
|
else {
|
|
result.then(successCallback).catch(errorCallback);
|
|
}
|
|
}
|
|
|
|
function _arrayBufferToBase64(buffer) {
|
|
var binary = '';
|
|
var bytes = new Uint8Array(buffer);
|
|
var len = bytes.byteLength;
|
|
for (var i = 0; i < len; i++) {
|
|
binary += String.fromCharCode(bytes[i]);
|
|
}
|
|
return window.btoa(binary);
|
|
}
|
|
|
|
function _base64ToArrayBuffer(base64) {
|
|
var binary_string = window.atob(base64);
|
|
var len = binary_string.length;
|
|
var bytes = new Uint8Array(len);
|
|
for (var i = 0; i < len; i++) {
|
|
bytes[i] = binary_string.charCodeAt(i);
|
|
}
|
|
return bytes.buffer;
|
|
}
|
|
|
|
function GetType(data) {
|
|
// get the data type of the parameter
|
|
var dataType = Object.prototype.toString.call(data);
|
|
dataType = dataType.substring(8, dataType.length - 1);
|
|
return dataType;
|
|
}
|
|
|
|
function addToCache(name,value)
|
|
{
|
|
if (typeof(window.localStorage)=="undefined")
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Los items del cache expiran cada 2 horas
|
|
|
|
window.localStorage.setItem(name, JSON.stringify({ value: value, expiration: (new Date()).getTime() + 7200000 }));
|
|
}
|
|
|
|
function getFromCache(name)
|
|
{
|
|
if (typeof (window.localStorage) == "undefined") {
|
|
return null;
|
|
}
|
|
|
|
var result = window.localStorage.getItem(name);
|
|
|
|
if (result == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
result = JSON.parse(result);
|
|
|
|
// Si ya expiró la entrada del cache entonces borrarla
|
|
|
|
if (result.expiration < (new Date()).getTime())
|
|
{
|
|
window.localStorage.removeItem(name);
|
|
return null;
|
|
}
|
|
|
|
return result.value;
|
|
}
|
|
|
|
function deleteFromCache(name)
|
|
{
|
|
if (typeof (window.localStorage) == "undefined") {
|
|
return;
|
|
}
|
|
|
|
window.localStorage.removeItem(name);
|
|
} |