From d8c31cc5ff84f43dcc8cfc80847003f05cd0b22f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=2E=20Garc=C3=ADa=20Glez?= Date: Mon, 14 Sep 2020 09:44:23 +0100 Subject: [PATCH 1/2] =?UTF-8?q?Implementaci=C3=B3n=20de=20firma=20por=20lo?= =?UTF-8?q?tes=20(batchsign)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El código que compone el siguiente commit implementa la firma por lotes en Android, de esta forma se puede enviar desde el navegador un único intent indicando como host batchsign. Esto es de especial interes en Chrome ya que no permite el envío de más de un intent sin la interacción del usuario; por otro lado también mejora la interacción de la aplicación con el usuario al tener este último que introducir la firma digital una única vez para actuar en un conjunto de documento. El formato del intent es el siguiente: Host: batchsign query: se compone por los siguientes parámetros fileid: identificador del batch a firmar rtservlet: Localización desde la que se descarga el xml con la definición de firmas key: Clave de cifrado (opcional). auth: Un objeto json en formato { "k": "nombre_clave", "v": "valor_a_enviar" } Un Ejemplo de intent intent://batchsign?fileid=zMX5J64THr4ZJ5lC1nsL&rtservlet=https%3A%2F%2Fwww.rtservlet.org%2Fservlet_afirma%2FRetrieveService&key=64782813#Intent;scheme=afirma;package=es.gob.afirma;end El procesado del xml y las firmas lo dirige la clase WebBatchSignActivity la cual tiene un comportamiento similar a WebSignActivity. Ejemplo del fichero XML con la definición de los documentos a firmar: https://remoteserver/01_dni.pdf CAdES sign Zm9ybWF0PUNBZEVTIERldGFjaGVkCm1vZGU9ZXhwbGljaXQKcmVmZXJlbmNlc0RpZ2VzdE1ldGhvZD1odHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYKZmlsdGVycz1ub25leHBpcmVkOnRydWU7aXNzdWVyLnJmYzIyNTQ6KCYoIShDTj1DaWJlckNlbnRybyopKSghKENOPUdvYkNhbkNBKSkoIShPPUdvYmllcm5vIGRlIENhbmFyaWFzKSkoIShPPVBLSSkpKCEoTz1ET19OT1RfVFJVU1QqKSkpO3NpZ25pbmdDZXJ0OnRydWUK es.gob.afirma.signers.batch.SignSaverHttpPost VVJMPWh0dHBzOi8vMTkyLjE2OC4yLjE0Mi8K https://remoteserver/01_doc02.pdf CAdES sign Zm9ybWF0PUNBZEVTIERldGFjaGVkCm1vZGU9ZXhwbGljaXQKcmVmZXJlbmNlc0RpZ2VzdE1ldGhvZD1odHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYKZmlsdGVycz1ub25leHBpcmVkOnRydWU7aXNzdWVyLnJmYzIyNTQ6KCYoIShDTj1DaWJlckNlbnRybyopKSghKENOPUdvYkNhbkNBKSkoIShPPUdvYmllcm5vIGRlIENhbmFyaWFzKSkoIShPPVBLSSkpKCEoTz1ET19OT1RfVFJVU1QqKSkpO3NpZ25pbmdDZXJ0OnRydWUK es.gob.afirma.signers.batch.SignSaverHttpPost VVJMPWh0dHBzOi8vZGVzdGluby9TdG9yYWdlU2VydmljZQpmaWxlSWQ9aFZOb1BQY211amc4VDVva0JUWTcK En el XML hay que destacar el apartado signsaver, la clase encargada del procesamiento es específica para la firma en android y no puede utilizarse otra devido a las limitaciones de la arquitectura. Para el almacenamiento de la firma es posible utilizar dos parámetros URL y fileId: El parámetro URL especifica la dirección a la que enviar el resultado de la firma, es un parámetro requerido, y el request se envía con los parámetros op, v, id y dat tal y como se realiza en las solicitudes de sign, cosign y countersign. El parámetro fileId es opcional y permite especificar el id con el que se envía la firma, en caso de no indicarlo se utiliza el mismo id del campo singlesign. TODO: * Mostrar un mensaje informando de la cantidad de ficheros a firmar antes de que se inicie el proceso de firma. * Permitir al usuario abrir/ver los documentos recibidos antes de firmarlos * Admitir documentos en base * Implementación de concurrenttimeout Ver: * Definición y limitaciones de intents con Chrome https://developer.chrome.com/multidevice/android/intents Definición y limitaciones de intents con Chrome * Documentación de clientefirma https://github.com/ctt-gob-es/clienteafirma-docs Documentación de clientefirma --- afirma-ui-android/app/build.gradle | 3 +- .../app/src/main/AndroidManifest.xml | 17 +- .../afirma/android/SignFragmentActivity.java | 11 +- .../afirma/android/WebBatchSignActivity.java | 734 ++++++++++++++++++ .../android/crypto/CipherDataManager.java | 3 +- .../afirma/android/gui/DownloadFileTask.java | 24 +- .../gob/afirma/android/gui/SendDataTask.java | 25 +- .../triphase/signer/SignBatchConfig.java | 132 ++++ .../android/signers/batch/BatchReader.java | 117 +++ .../signers/batch/SingleSignConstants.java | 65 ++ .../batch/signer/SignBatchXmlHandler.java | 271 +++++++ .../signers/batch/signer/SignSaver.java | 51 ++ .../signers/batch/signer/SingleSign.java | 543 +++++++++++++ .../signers/constants/SignAlgorithm.java | 69 ++ .../android/signers/constants/SignFormat.java | 81 ++ .../afirma/signers/batch/BatchException.java | 9 + .../signers/batch/SignSaverHttpPost.java | 52 ++ .../app/src/main/res/values-en/strings.xml | 2 + .../app/src/main/res/values-fr/strings.xml | 1 + .../app/src/main/res/values/strings.xml | 2 + 20 files changed, 2202 insertions(+), 10 deletions(-) create mode 100644 afirma-ui-android/app/src/main/java/es/gob/afirma/android/WebBatchSignActivity.java create mode 100644 afirma-ui-android/app/src/main/java/es/gob/afirma/android/server/triphase/signer/SignBatchConfig.java create mode 100644 afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/BatchReader.java create mode 100644 afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/SingleSignConstants.java create mode 100644 afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/signer/SignBatchXmlHandler.java create mode 100644 afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/signer/SignSaver.java create mode 100644 afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/signer/SingleSign.java create mode 100644 afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/constants/SignAlgorithm.java create mode 100644 afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/constants/SignFormat.java create mode 100644 afirma-ui-android/app/src/main/java/es/gob/afirma/signers/batch/BatchException.java create mode 100644 afirma-ui-android/app/src/main/java/es/gob/afirma/signers/batch/SignSaverHttpPost.java diff --git a/afirma-ui-android/app/build.gradle b/afirma-ui-android/app/build.gradle index 3f7aae8..f4ee265 100644 --- a/afirma-ui-android/app/build.gradle +++ b/afirma-ui-android/app/build.gradle @@ -59,6 +59,7 @@ dependencies { implementation 'es.gob.afirma:afirma-crypto-cades-multi:1.6.5' implementation 'es.gob.afirma:afirma-crypto-core-pkcs7:1.6.5' implementation 'es.gob.afirma:afirma-crypto-core-pkcs7-tsp:1.6.5' + implementation 'es.gob.afirma:afirma-crypto-batch-client:1.6.5' implementation 'es.gob.afirma.jmulticard:jmulticard-jse:1.5' //implementation 'com.android.support:multidex:1.0.3' @@ -69,4 +70,4 @@ dependencies { //androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' } -//apply plugin: 'com.google.gms.google-services' \ No newline at end of file +//apply plugin: 'com.google.gms.google-services' diff --git a/afirma-ui-android/app/src/main/AndroidManifest.xml b/afirma-ui-android/app/src/main/AndroidManifest.xml index 88312a1..752d5cb 100644 --- a/afirma-ui-android/app/src/main/AndroidManifest.xml +++ b/afirma-ui-android/app/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ - @@ -60,6 +59,22 @@ + + + + + + + + + + + 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Logger.i("es.gob.afirma", "Concedido permiso de escritura en memoria"); + processSignRequest(); + } + else { + // Si no nos dan los permisos, directamente cerramos la aplicacion + android.os.Process.killProcess(android.os.Process.myPid()); + System.exit(1); + } + } + } + + private void setIntentSuffix(Map params) { + StringBuilder intentParameters = new StringBuilder(); + + for (Map.Entry entry : params.entrySet()) { + if (!"files".equals(entry.getKey()) && !"op".equals(entry.getKey())) + intentParameters.append("&") + .append(entry.getKey()) + .append("=") + .append(Uri.encode(entry.getValue())); + } + intentSuffix = intentParameters.toString(); + } + + /** Inicia el proceso de firma con los parametros previamente configurados. */ + private void processSignRequest() { + try { + String intentUri = getIntent().getDataString(); + this.parametersBatch = ProtocolInvocationUriParser.getParametersForBatch(intentUri); + Map params = parserUri(intentUri); + setIntentSuffix(params); + setAuthMethod(params); + } + catch (final ParameterException e) { + Logger.e(ES_GOB_AFIRMA, "Error en los parametros de firma: " + e.toString(), e); //$NON-NLS-1$ + showErrorMessage(getString(R.string.error_bad_params)); + launchError(ErrorManager.ERROR_BAD_PARAMETERS, true); + return; + } + catch (final Throwable e) { + Logger.e(ES_GOB_AFIRMA, "Error grave en el onCreate de WebMultiSignActivity: " + e.toString(), e); //$NON-NLS-1$ + e.printStackTrace(); + showErrorMessage(getString(R.string.error_bad_params)); + launchError(ErrorManager.ERROR_BAD_PARAMETERS, true); + return; + } + + getBatchFile(); + } + + private void getBatchFile() { + Logger.i(ES_GOB_AFIRMA, "Se va a descargar la definición del conjunto de ficheros a firmar"); + this.mode = Modes.INIT; + this.downloadFileTask = new DownloadFileTask( + parametersBatch.getFileId(), + parametersBatch.getRetrieveServletUrl(), + this.authMethod, + this + ); + this.downloadFileTask.execute(); + } + + /** Inicia el proceso de firma del siguiente fichero de la lista de operaciones */ + private void signNext() { + actualSign = batchReader.getSigns().remove(0); + try { + Logger.i(ES_GOB_AFIRMA, "Se van a descargar los datos desde servidor con el identificador: " + this.actualSign.getId()); //$NON-NLS-1$ + + this.downloadFileTask = new DownloadFileTask( + this.actualSign.getId(), + new URL(this.actualSign.getDataSource()), + this.authMethod, + this + ); + this.downloadFileTask.execute(); + } catch (MalformedURLException e) { + Logger.e(ES_GOB_AFIRMA, "Error en la url de descarga: " + this.actualSign.getDataSource() + " id fichero: " + this.actualSign.getId()); + } + } + + private void setAuthMethod(Map params) { + this.authMethod = new Properties(); + try { + String auth = params.get("auth"); + if (auth != null && !auth.isEmpty()) { + JSONObject authJson = new JSONObject(); + this.authMethod.put(authJson.get("k"), authJson.get("v")); + } + } + catch (final Throwable e) { + Logger.e(ES_GOB_AFIRMA, "Error grave en el onCreate de WebMultiSignActivity: " + e.toString(), e); //$NON-NLS-1$ + e.printStackTrace(); + showErrorMessage(getString(R.string.error_bad_params)); + launchError(ErrorManager.ERROR_BAD_PARAMETERS, true); + return; + } + } + + private boolean isValidOperation(String operation) { + return UrlParametersToSign.Operation.getOperation(operation.toUpperCase()) != null; + } + + @Override + public void onStart() { + super.onStart(); + Logger.i(ES_GOB_AFIRMA, " -- WebMultiSignActivity onStart"); + } + + /** Envía los datos indicado a un servlet. En caso de error, cierra la aplicación. + * @param data Datos que se desean enviar. */ + private void sendData(final String data, final boolean critical) { + + Logger.i(ES_GOB_AFIRMA, " -- WebMultiSignActivity sendData"); + + Logger.i(ES_GOB_AFIRMA, "Se almacena el resultado en el servidor con el Id: " + this.actualSign.getSignId()); //$NON-NLS-1$ + + new SendDataTask( + this.actualSign.getSignId(), + this.actualSign.getRetrieveServerUrl(), + data, + authMethod, + this, + critical + ).execute(); + } + + /** Muestra un mensaje de error y lo envía al servidor para que la página Web + * tenga constancia de él. + * @param errorId Identificador del error. + * @param critical true si debe mostrarse el error al usuario, false + * en caso contrario. + */ + private void launchError(final String errorId, final boolean critical) { + + Logger.i(ES_GOB_AFIRMA, " -- WebBatchSignActivity launchError"); + + try { + if (INTENT_SIGN_SERVICE.equals(getIntent().getAction())){ + Logger.i(ES_GOB_AFIRMA, "Devolvemos el error a la app solicitante"); //$NON-NLS-1$ + sendDataIntent(Activity.RESULT_CANCELED, ErrorManager.genError(errorId, null)); + } + else { + sendData(URLEncoder.encode(ErrorManager.genError(errorId, null), DEFAULT_URL_ENCODING), critical); + } + } + catch (final UnsupportedEncodingException e) { + // No puede darse, el soporte de UTF-8 es obligatorio + Logger.e(ES_GOB_AFIRMA, + "No se ha podido enviar la respuesta al servidor por error en la codificacion " + DEFAULT_URL_ENCODING, e //$NON-NLS-1$ + ); + } + catch (final Throwable e) { + Logger.e(ES_GOB_AFIRMA, + "Error desconocido al enviar el error obtenido al servidor: " + e, e //$NON-NLS-1$ + ); + } + } + + /** Muestra un mensaje de advertencia al usuario. + * @param message Mensaje que se desea mostrar. */ + private void showErrorMessage(final String message) { + + dismissProgressDialog(); + + if (this.messageDialog == null) { + this.messageDialog = MessageDialog.newInstance(message); + this.messageDialog.setListener(new CloseActivityDialogAction()); + this.messageDialog.setDialogBuilder(this); + } + + runOnUiThread(new Runnable() { + @Override + public void run() { + try { + WebBatchSignActivity.this.getMessageDialog().show(getSupportFragmentManager(), "ErrorDialog"); //$NON-NLS-1$; + } + catch (final Exception e) { + // Si falla el mostrar el error (posiblemente por no disponer de un contexto grafico para mostrarlo) + // se mostrara en un + Toast.makeText(WebBatchSignActivity.this, message, Toast.LENGTH_LONG).show(); + } + + } + }); + } + + /** Muestra un mensaje de advertencia al usuario. + * @param message Mensaje que se desea mostrar. */ + private void showErrorMessageOnToast(final String message) { + + dismissProgressDialog(); + dismissMessageDialog(); + + runOnUiThread( + new Runnable() { + @Override + public void run() { + Toast.makeText(WebBatchSignActivity.this, message, Toast.LENGTH_LONG).show(); + } + } + ); + } + + @Override + protected void onSigningError(final KeyStoreOperation op, final String msg, final Throwable t) { + if (op == KeyStoreOperation.LOAD_KEYSTORE) { + launchError(ErrorManager.ERROR_ESTABLISHING_KEYSTORE, true); + } + else if (op == KeyStoreOperation.SELECT_CERTIFICATE) { + + if (t instanceof SelectKeyAndroid41BugException) { + launchError(ErrorManager.ERROR_PKE_ANDROID_4_1, true); + } + else if (t instanceof KeyChainException) { + launchError(ErrorManager.ERROR_PKE, true); + } + else if (t instanceof PendingIntent.CanceledException) { + Logger.e(ES_GOB_AFIRMA, "El usuario no selecciono un certificado", t); //$NON-NLS-1$ + launchError(ErrorManager.ERROR_CANCELLED_OPERATION, false); + + } + else { + Logger.e(ES_GOB_AFIRMA, "Error al recuperar la clave del certificado de firma", t); //$NON-NLS-1$ + launchError(ErrorManager.ERROR_PKE, true); + } + } + else if (op == KeyStoreOperation.SIGN) { + if (t instanceof MSCBadPinException) { + Logger.e(ES_GOB_AFIRMA, "PIN erroneo: " + t); //$NON-NLS-1$ + showErrorMessage(getString(R.string.error_msc_pin)); + launchError(ErrorManager.ERROR_MSC_PIN, false); + } + else if (t instanceof AOUnsupportedSignFormatException) { + Logger.e(ES_GOB_AFIRMA, "Formato de firma no soportado: " + t); //$NON-NLS-1$ + showErrorMessage(getString(R.string.error_format_not_supported)); + launchError(ErrorManager.ERROR_NOT_SUPPORTED_FORMAT, true); + } + else { + Logger.e(ES_GOB_AFIRMA, "Error al firmar", t); //$NON-NLS-1$ + launchError(ErrorManager.ERROR_SIGNING, true); + } + } + Logger.e(ES_GOB_AFIRMA, "Error desconocido", t); //$NON-NLS-1$ + if (batchReader.isStopOnError()) + launchError(ErrorManager.ERROR_SIGNING, true); + else { + showErrorMessageOnToast(getString(R.string.error_signing)); + signNext(); + } + } + + private void showProgressDialog(final String message) { + dismissProgressDialog(); + runOnUiThread( + new Runnable() { + @Override + public void run() { + try { + setProgressDialog(ProgressDialog.show(WebBatchSignActivity.this, "", message, true)); //$NON-NLS-1$ + } + catch (final Throwable e) { + Logger.e(ES_GOB_AFIRMA, "No se ha podido mostrar el dialogo de progreso", e); //$NON-NLS-1$ + } + } + } + ); + } + + @Override + public synchronized void onDownloadingDataSuccess(final byte[] data) { + + Logger.i(ES_GOB_AFIRMA, " -- WebBatchSignActivity onDownloadingDataSuccess"); + + Logger.i(ES_GOB_AFIRMA, "Se ha descargado correctamente la configuracion de firma almacenada en servidor"); //$NON-NLS-1$ + Logger.i(ES_GOB_AFIRMA, "Cantidad de datos descargada: " + (data == null ? -1 : data.length)); //$NON-NLS-1$ + + // Si hemos tenido que descargar los datos desde el servidor, los desciframos y llamamos + // al dialogo de seleccion de certificados para la firma + final byte[] decipheredData; + try { + byte[] desKey = this.parametersBatch.getDesKey(); + decipheredData = CipherDataManager.decipherData(data, desKey); + + if (this.mode.equals(Modes.INIT)) { + initBatchSignData(decipheredData); + this.totalDocumentosAFirmar = this.batchReader.getSigns().size(); + this.totalErrors = 0; + this.mode = Modes.SIGN; + signNext(); + } else + initSign(decipheredData); + } catch (final IOException e) { + Logger.e(ES_GOB_AFIRMA, "Los datos proporcionados no están correctamente codificados en base 64", e); //$NON-NLS-1$ + showErrorMessage(getString(R.string.error_bad_params)); + return; + } catch (final GeneralSecurityException e) { + Logger.e(ES_GOB_AFIRMA, "Error al descifrar los datos recuperados del servidor para la firma", e); //$NON-NLS-1$ + showErrorMessage(getString(R.string.error_bad_params)); + return; + } catch (final IllegalArgumentException e) { + Logger.e(ES_GOB_AFIRMA, "Los datos recuperados no son un base64 valido", e); //$NON-NLS-1$ + showErrorMessage(getString(R.string.error_bad_params)); + return; + } catch (final BatchException e) { + Logger.e(ES_GOB_AFIRMA, "Error en el Batch", e); + showErrorMessage(getString(R.string.error_batch_process_error)); + return; + } catch (final Throwable e) { + Logger.e(ES_GOB_AFIRMA, "Error desconocido durante el descifrado de los datos", e); //$NON-NLS-1$ + showErrorMessage(getString(R.string.error_bad_params)); + return; + } + } + + public void initBatchSignData(final byte[] xml) throws BatchException { + boolean result = false; + Logger.i(ES_GOB_AFIRMA, "Se han descifrado los datos y se inicia su analisis:\n" + new String(xml)); //$NON-NLS-1$ + try { + batchReader = new BatchReader(); + batchReader.parse(xml); + Logger.i(ES_GOB_AFIRMA, "Se ha procesado el fichero bath, se encontraron " + this.batchReader.getSigns().size() + " documentos"); + result = true; + } catch (IOException e) { + Logger.e(ES_GOB_AFIRMA, "Error procesando el fichero batch " + e.toString(), e); + throw new BatchException("Error procesando el fichero batch", e); + } + } + + public void initSign(final byte[] decipheredData) { + Logger.i(ES_GOB_AFIRMA, "Se han descifrado los datos y se inicia su analisis:\n" + new String(decipheredData)); //$NON-NLS-1$ + Logger.i(ES_GOB_AFIRMA, "Se inicia la firma de los datos descargados desde el servidor"); //$NON-NLS-1$ + showProgressDialog(getString(R.string.dialog_msg_signning) + " " + + (totalDocumentosAFirmar - (batchReader.getSigns().size())) + "/" + totalDocumentosAFirmar); + try { + sign( + actualSign.getSubOperation().toString(), + decipheredData, + actualSign.getSignFormat().toString(), + batchReader.getSignAlgorithm().toString(), + actualSign.getExtraParams() + ); + } + catch (final Exception e) { + Logger.e(ES_GOB_AFIRMA, "Error durante la firma", e); //$NON-NLS-1$ + showErrorMessage(getString(R.string.error_signing_config)); + } + } + + @Override + public synchronized void onDownloadingDataError(final String msg, final Throwable t) { + Logger.e(ES_GOB_AFIRMA, "Error durante la descarga de la configuracion de firma guardada en servidor:" + msg + (t != null ? ": " + t.toString() : ""), t); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + if (mode.equals(Modes.INIT)) { + showErrorMessage(getString(R.string.error_server_connect)); + closeActivity(); + } else + showErrorMessageOnToast(getString(R.string.error_server_connect)); + totalErrors++; + if (!batchReader.isStopOnError()) + signNext(); + } + + @Override + public void onSigningSuccess(final SignResult signature) { + + Logger.i(ES_GOB_AFIRMA, " -- WebBatchSignActivity onSigningSuccess"); + + Logger.i(ES_GOB_AFIRMA, "Firma generada correctamente. Se cifra el resultado."); + + // Ciframos si nos dieron clave privada, si no subimos los datos sin cifrar + final String data; + final byte[] cipherKey = this.parametersBatch.getDesKey(); + try { + data = cipherKey != null ? + CipherDataManager.cipherData(signature.getSignature(), + cipherKey) : Base64.encodeToString(signature.getSignature(), Base64.DEFAULT); + + } + catch (final GeneralSecurityException e) { + Logger.e(ES_GOB_AFIRMA, "Error en el cifrado de la firma", e); //$NON-NLS-1$ + launchError(ErrorManager.ERROR_CIPHERING, true); + return; + } + catch (final Throwable e) { + Logger.e(ES_GOB_AFIRMA, "Error desconocido al cifrar el resultado de la firma", e); //$NON-NLS-1$ + launchError(ErrorManager.ERROR_CIPHERING, true); + return; + } + + String signingCert; + try { + signingCert = cipherKey != null ? CipherDataManager.cipherData( + signature.getSigningCertificate().getEncoded(), + this.parametersBatch.getDesKey()) : Base64.encodeToString(signature.getSigningCertificate().getEncoded(), Base64.DEFAULT); + } + catch (final GeneralSecurityException e) { + Logger.e(ES_GOB_AFIRMA, "Error en el cifrado del certificado de firma: " + e, e); //$NON-NLS-1$ + signingCert = null; + } + + Logger.i(ES_GOB_AFIRMA, "Firma cifrada. Se envia al servidor."); //$NON-NLS-1$ + sendData( + signingCert != null ? signingCert + CERT_SIGNATURE_SEPARATOR + data : data, + true + ); + Logger.i(ES_GOB_AFIRMA, "Firma enviada."); //$NON-NLS-1$ + } + + private void sendDataIntent (final int isOk, String data) { + Intent result = new Intent(); + result.setData(Uri.parse(data)); + if (getParent() == null) { + setResult(isOk, result); + } + else { + getParent().setResult(isOk, result); + } + finish(); + closeActivity(); + } + + @Override + public void onSendingDataSuccess(final byte[] result, final boolean critical) { + Logger.i(ES_GOB_AFIRMA, "Resultado del deposito de la firma: " + (result == null ? null : new String(result))); //$NON-NLS-1$ + + if (result == null || !new String(result).trim().equals(OK_SERVER_RESULT)) { + Logger.e(ES_GOB_AFIRMA, "No se pudo entregar la firma al servlet: " + (result == null ? null : new String(result))); //$NON-NLS-1$ + if (critical) { + totalErrors++; + showErrorMessageOnToast(getString(R.string.error_sending_data)); + } + } + else { + Logger.i(ES_GOB_AFIRMA, "Resultado entregado satisfactoriamente."); //$NON-NLS-1$ + } + if (!isSignEnd()) + signNext(); + else { + dismissProgressDialog(); + closeActivity(); + } + } + + private boolean isSignEnd() { + return batchReader.getSigns().size() == 0; + } + + private boolean isLastDocument() { + return isSignEnd(); + } + + @Override + public void onSendingDataError(final Throwable error, final boolean critical) { + + Logger.e(ES_GOB_AFIRMA, "Se ejecuta la funcion de error en el envio de datos", error); //$NON-NLS-1$ + error.printStackTrace(); + + if (critical) { + showErrorMessageOnToast(getString(R.string.error_sending_data)); + if (batchReader.isStopOnError() || isLastDocument()) + closeActivity(); + else + signNext(); + } + } + + /** Comprueba si esta abierto el diálogo de espera y lo cierra en dicho caso. */ + private void dismissProgressDialog() { + if (this.progressDialog != null) { + this.progressDialog.dismiss(); + } + } + + /** Comprueba si esta abierto el diálogo de mensajes y lo cierra en dicho caso. */ + private void dismissMessageDialog() { + if (this.messageDialog != null) { + this.messageDialog.dismiss(); + this.messageDialog = null; + } + } + + /** Accion para el cierre de la actividad. */ + private final class CloseActivityDialogAction implements DialogInterface.OnClickListener { + + CloseActivityDialogAction() { + // Constructor vacio para evitar el sintetico + } + + @Override + public void onClick(final DialogInterface dialog, final int which) { + closeActivity(); + } + } + + void showProcessErrors() { + dismissProgressDialog(); + dismissMessageDialog(); + String msg = getString(R.string.error_batch_process_error); + msg += batchReader.isStopOnError() ? "" : "\n" + String.format(getString(R.string.error_batch_process_continue_on_error), totalErrors); + showErrorMessage(msg); + totalErrors = 0; + } + + void closeActivity() { + if (totalErrors > 0) { + showProcessErrors(); + } else + finishAffinity(); + } + + @Override + public boolean onKeyDown(final int keyCode, final KeyEvent event) { + if(keyCode == KeyEvent.KEYCODE_HOME) { + launchError(ErrorManager.ERROR_CANCELLED_OPERATION, false); + } + return super.onKeyDown(keyCode, event); + } + + @Override + public void onBackPressed() { + launchError(ErrorManager.ERROR_CANCELLED_OPERATION, false); + super.onBackPressed(); + } + + @Override + protected void onStop() { + dismissProgressDialog(); + dismissMessageDialog(); + super.onStop(); + } + + @Override + protected void onDestroy() { + if (this.downloadFileTask != null) { + Logger.d(ES_GOB_AFIRMA, "WebBatchSignActivity onDestroy: Cancelamos la descarga"); //$NON-NLS-1$ + try { + this.downloadFileTask.cancel(true); + } + catch(final Exception e) { + Logger.e(ES_GOB_AFIRMA, "No se ha podido cancelar el procedimiento de descarga de los datos", e); //$NON-NLS-1$ + } + } + super.onDestroy(); + } + + //TODO: Proviene de afirma-core, añadir en ProtocolInvocationUriParser un método + // para obtener el listado de fircheros y operacionesde la uri. + /** Analiza la URL de entrada para obtener la lista de parámetros asociados. + * @param uri URL de llamada. + * @return Devuelve una tabla hash con cada parámetro asociado a un valor. */ + private static Map parserUri(final String uri) { + final Map params = new HashMap<>(); + final String[] parameters = uri.substring(uri.indexOf('?') + 1).split("&"); //$NON-NLS-1$ + for (final String param : parameters) { + if (param.indexOf('=') > 0) { + try { + params.put( + param.substring(0, param.indexOf('=')), + param.indexOf('=') == param.length() - 1 ? + "" : //$NON-NLS-1$ + URLDecoder.decode(param.substring(param.indexOf('=') + 1), StandardCharsets.UTF_8.name()) + ); + } + catch (final UnsupportedEncodingException e) { + params.put( + param.substring(0, param.indexOf('=')), + param.indexOf('=') == param.length() - 1 ? "" : param.substring(param.indexOf('=') + 1) //$NON-NLS-1$ + ); + } + } + } + + // Agregamos como codigo de operacion el nombre de host de la URL + Logger.i(ES_GOB_AFIRMA,"URI recibida: " + uri); //$NON-NLS-1$ //$NON-NLS-2$ + + String path = uri.substring(uri.indexOf("://") + "://".length(), uri.indexOf('?') != -1 ? uri.indexOf('?') : uri.length()); //$NON-NLS-1$ //$NON-NLS-2$ + if (path.endsWith("/")) { //$NON-NLS-1$ + path = path.substring(0, path.length() - 1); + } + params.put(ProtocolConstants.OPERATION_PARAM, path.substring(path.lastIndexOf('/') + 1)); + + return params; + } +} \ No newline at end of file diff --git a/afirma-ui-android/app/src/main/java/es/gob/afirma/android/crypto/CipherDataManager.java b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/crypto/CipherDataManager.java index bd8a40e..dfdad67 100644 --- a/afirma-ui-android/app/src/main/java/es/gob/afirma/android/crypto/CipherDataManager.java +++ b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/crypto/CipherDataManager.java @@ -55,6 +55,7 @@ public static byte[] decipherData(final byte[] cipheredDataB64, Logger.i(ES_GOB_AFIRMA, "Componemos la cadena para descifrar"); //$NON-NLS-1$ final String recoveredData = new String(cipheredDataB64, DEFAULT_URL_ENCODING).replace("_", "/").replace("-", "+"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + Logger.d(ES_GOB_AFIRMA,"Datos recuperados:" + recoveredData); byte[] decipheredData; if (cipherKey != null) { Logger.i(ES_GOB_AFIRMA, "Vamos a descifrar"); //$NON-NLS-1$ @@ -63,7 +64,7 @@ public static byte[] decipherData(final byte[] cipheredDataB64, } else { Logger.i(ES_GOB_AFIRMA, "No tenemos clave para descifrar. Consideramos los datos como descifrados"); //$NON-NLS-1$ - decipheredData = Base64.decode(recoveredData, true); + decipheredData = Base64.decode(recoveredData, false); } return decipheredData; } diff --git a/afirma-ui-android/app/src/main/java/es/gob/afirma/android/gui/DownloadFileTask.java b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/gui/DownloadFileTask.java index afc764a..acbf5af 100644 --- a/afirma-ui-android/app/src/main/java/es/gob/afirma/android/gui/DownloadFileTask.java +++ b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/gui/DownloadFileTask.java @@ -12,9 +12,11 @@ import java.io.IOException; import java.net.URL; +import java.util.Properties; import es.gob.afirma.android.Logger; import es.gob.afirma.core.AOCancelledOperationException; +import es.gob.afirma.core.misc.http.UrlHttpManagerImpl; import es.gob.afirma.core.misc.http.UrlHttpMethod; /** Tarea para la descarga de un fichero del servidor intermedio. */ @@ -37,6 +39,7 @@ public final class DownloadFileTask extends BasicHttpTransferDataTask { private final String fileId; private final URL retrieveServletUrl; + private final Properties properties; private final DownloadDataListener ddListener; private String errorMessage = null; @@ -48,9 +51,26 @@ public final class DownloadFileTask extends BasicHttpTransferDataTask { * @param fileId Identificadod del fichero en el servidor intermedio * @param retrieveServletUrl URL del servidor intermedio * @param ddListener Clase a la que hay que notificar el resultado de la descraga */ - public DownloadFileTask(final String fileId, final URL retrieveServletUrl, final DownloadDataListener ddListener) { + public DownloadFileTask(final String fileId, + final URL retrieveServletUrl, + final DownloadDataListener ddListener) { + this(fileId, retrieveServletUrl, null, ddListener); + } + + /** Crea una tarea para la descarga de un fichero del servidor intermedio. + * @param fileId Identificadod del fichero en el servidor intermedio + * @param retrieveServletUrl URL del servidor intermedio + * @param properties Propiedades adicionales + * @param ddListener Clase a la que hay que notificar el resultado de la descraga */ + public DownloadFileTask(final String fileId, + final URL retrieveServletUrl, + final Properties properties, + final DownloadDataListener ddListener) { this.fileId = fileId; this.retrieveServletUrl = retrieveServletUrl; + this.properties = new Properties(); + if (properties != null) + this.properties.putAll(properties); this.ddListener = ddListener; } @@ -68,7 +88,7 @@ protected byte[] doInBackground(final Void... arg0) { Logger.i(ES_GOB_AFIRMA, "URL: " + url); //$NON-NLS-1$ // Llamamos al servicio para guardar los datos - data = this.readUrl(url.toString(), UrlHttpMethod.POST); + data = this.readUrl(url.toString(), UrlHttpManagerImpl.DEFAULT_TIMEOUT, UrlHttpMethod.POST, properties); if (ERROR_PREFIX.equalsIgnoreCase(new String(data, 0, 4, DEFAULT_URL_ENCODING))) { diff --git a/afirma-ui-android/app/src/main/java/es/gob/afirma/android/gui/SendDataTask.java b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/gui/SendDataTask.java index 1b2f14a..9c91914 100644 --- a/afirma-ui-android/app/src/main/java/es/gob/afirma/android/gui/SendDataTask.java +++ b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/gui/SendDataTask.java @@ -11,9 +11,11 @@ package es.gob.afirma.android.gui; import java.io.IOException; +import java.util.Properties; import es.gob.afirma.android.Logger; import es.gob.afirma.core.AOCancelledOperationException; +import es.gob.afirma.core.misc.http.UrlHttpManagerImpl; import es.gob.afirma.core.misc.http.UrlHttpMethod; /** Tarea para el envío de datos al servidor de intercambio. Si la entrega de estos datos es @@ -30,6 +32,7 @@ public final class SendDataTask extends BasicHttpTransferDataTask { private final String id; private final String servletUrl; + private final Properties properties; private final String dataB64; private final SendDataListener listener; private final boolean critical; @@ -43,10 +46,28 @@ public final class SendDataTask extends BasicHttpTransferDataTask { * @param listener Clase a la que se notifica el resultado del envío de datos * @param critical {@code true} si el procedimiento es crítico, {@code false} en caso contrario. */ - public SendDataTask(final String id, final String servletUrl, final String dataB64, final SendDataListener listener, final boolean critical) { + public SendDataTask(final String id, final String servletUrl, final String dataB64, + final SendDataListener listener, final boolean critical) { + this(id, servletUrl, dataB64, null, listener, critical); + } + + /** Crea la tarea con los datos necesarios para el intercambio, permitiendo que se indique si la + * entrega de estos datos es un proceso crítico para la ejecución del procedimiento. + * @param id Identificador del intercambio. + * @param servletUrl URL del servlet para la subida de datos. + * @param dataB64 Datos en base 64 que se desean enviar. + * @param properties Propiedades adicionales. + * @param listener Clase a la que se notifica el resultado del envío de datos + * @param critical {@code true} si el procedimiento es crítico, {@code false} en caso contrario. + */ + public SendDataTask(final String id, final String servletUrl, final String dataB64, + final Properties properties, final SendDataListener listener, final boolean critical) { this.id = id; this.servletUrl = servletUrl; this.dataB64 = dataB64; + this.properties = new Properties(); + if (properties != null) + this.properties.putAll(properties); this.listener = listener; this.critical = critical; } @@ -63,7 +84,7 @@ protected byte[] doInBackground(final Void... arg0) { url.append("&dat=").append(this.dataB64); //$NON-NLS-1$ // Llamamos al servicio para guardar los datos - result = readUrl(url.toString(), UrlHttpMethod.POST); + result = readUrl(url.toString(), UrlHttpManagerImpl.DEFAULT_TIMEOUT, UrlHttpMethod.POST, properties); } catch (final IOException e) { Logger.e(ES_GOB_AFIRMA, "No se pudo conectar con el servidor intermedio para el envio de datos: " + e); //$NON-NLS-1$ diff --git a/afirma-ui-android/app/src/main/java/es/gob/afirma/android/server/triphase/signer/SignBatchConfig.java b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/server/triphase/signer/SignBatchConfig.java new file mode 100644 index 0000000..01e1ee0 --- /dev/null +++ b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/server/triphase/signer/SignBatchConfig.java @@ -0,0 +1,132 @@ +/* + * Este fichero es parte de Server triphase signer de ClienteFirma, + * ha sido adaptado y modificado para el funcionamiento de la firma + * batch en android. + * + * Este software sigue los mismos criterios de licencia previos a su + * modificación: https://github.com/ctt-gob-es/clienteafirma/ + * + * Modificado por: Eduardo García + * Fecha modificación: 10 sep 2020 + */ +package es.gob.afirma.android.server.triphase.signer; + +import java.util.ArrayList; +import java.util.List; + +import es.gob.afirma.android.signers.constants.SignAlgorithm; +import es.gob.afirma.android.signers.batch.signer.SingleSign; + +/** + * Configuración para la firma de un lote. + */ +public class SignBatchConfig { + + /** Tiempo de espera que, por defecto, se aplicaráa a las distintas + * operaciones de firma concurrente de datos. */ + private static final long DEFAULT_TIMEOUT = 30; + + private String id; + + private boolean stopOnError; + + private SignAlgorithm algorithm; + + private long concurrentTimeout; + + private final List signs; + + /** + * Construye un lote vacío. + */ + public SignBatchConfig() { + this.id = null; + this.stopOnError = true; + this.concurrentTimeout = DEFAULT_TIMEOUT; + this.signs = new ArrayList<>(); + } + + /** + * Obtiene el ID del lote. + * @return Identificador del lote o {@code null} si no está definido. + */ + public String getId() { + return this.id; + } + + /** + * Indica si se debe detener la ejecución del lote al detectar un error. + * @return {@code true}, valor por defecto, si se debe detener la ejecución, + * {@code false} en caso contrario. + */ + public boolean isStopOnError() { + return this.stopOnError; + } + + /** + * Devuelve el algoritmo de firma. + * @return Algoritmo de firma. + */ + public SignAlgorithm getAlgorithm() { + return this.algorithm; + } + + /** + * Devuelve el tiempo de espera máximo para la ejecución del lote. + * Por defecto, devuelve el valor 0 (se espera indefinidamente). + * @return Tiempo de espera en milisegundos o 0 si se espera indefinidamente. + */ + public long getConcurrentTimeout() { + return this.concurrentTimeout; + } + + /** + * Establece el identificador del lote. + * @param id Identificador del lote. + */ + public void setId(String id) { + this.id = id; + } + + /** + * Establece si debe detenerse la ejecución del lote en caso de error. + * @param stopOnError {@code true} si se debe detener la ejecución, + * {@code false} en caso contrario. + */ + public void setStopOnError(boolean stopOnError) { + this.stopOnError = stopOnError; + } + + /** + * Establece el algoritmo de firma para los documentos del lote. + * @param algorithm Algoritmo de firma. + */ + public void setAlgorithm(SignAlgorithm algorithm) { + this.algorithm = algorithm; + } + + /** + * Establece el tiempo máximo de espera para la firma del lote. + * @param concurrentTimeout Tiempo máximo de espera o 0 si se + * espera indefinidamente. + */ + public void setConcurrentTimeout(long concurrentTimeout) { + this.concurrentTimeout = concurrentTimeout; + } + + /** + * Agrega un nuevo documento al lote. + * @param sign Información necesaria para la firma del documento. + */ + public void addSingleSign(SingleSign sign) { + this.signs.add(sign); + } + + /** + * Obtiene el listado de configuraciones de los documentos a firmar. + * @return Listado de configuraciones de los documentos a firmar. + */ + public List getSingleSigns() { + return this.signs; + } +} diff --git a/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/BatchReader.java b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/BatchReader.java new file mode 100644 index 0000000..68da828 --- /dev/null +++ b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/BatchReader.java @@ -0,0 +1,117 @@ +/* Copyright (C) 2011 [Gobierno de Espana] + * This file is part of "Cliente @Firma". + * "Cliente @Firma" 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. + * - or The European Software License; either version 1.1 or (at your option) any later version. + * You may contact the copyright holder at: soporte.afirma@seap.minhap.es + * + * Este fichero es parte de Server triphase signer de ClienteFirma, + * ha sido adaptado y modificado a partir de SignBatch.java para el funcionamiento + * de la firma batch en android. + * + * Este software sigue los mismos criterios de licencia previos a su + * modificación: https://github.com/ctt-gob-es/clienteafirma/ + * + * Modificado por: Eduardo García + * Fecha modificación: 10 sep 2020 + */ + +package es.gob.afirma.android.signers.batch; + +import android.util.Log; + +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.UUID; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import es.gob.afirma.android.server.triphase.signer.SignBatchConfig; +import es.gob.afirma.android.signers.batch.signer.SignBatchXmlHandler; +import es.gob.afirma.android.signers.batch.signer.SingleSign; +import es.gob.afirma.android.signers.constants.SignAlgorithm; + +public class BatchReader { + private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + private static final String ES_GOB_AFIRMA = "es.gob.afirma"; //$NON-NLS-1$ + + protected List signs; + protected SignAlgorithm algorithm; + protected long concurrentTimeout = Long.MAX_VALUE; + protected boolean stopOnError = false; + + private String id; + + String getId() { + return this.id; + } + + void setId(final String i) { + if (i != null) { + this.id = i; + } + } + + public List getSigns() { + return signs; + } + + /** Obtiene el algoritmo de firma. + * @return Algoritmo de firma. */ + public SignAlgorithm getSignAlgorithm() { + return this.algorithm; + } + + public long getConcurrentTimeout() { + return concurrentTimeout; + } + + public boolean isStopOnError() { + return stopOnError; + } + + public void parse(final byte[] xml) throws IOException { + if (xml == null || xml.length < 1) { + throw new IllegalArgumentException( + "El XML de definicion de lote de firmas no puede ser nulo ni vacio" //$NON-NLS-1$ + ); + } + + // Definimos un manejador que extraera la informacion del XML + final SignBatchXmlHandler handler = new SignBatchXmlHandler(); + + try (final InputStream is = new ByteArrayInputStream(xml)) { + + final SAXParserFactory spf = SAXParserFactory.newInstance(); + spf.setNamespaceAware(true); + final SAXParser saxParser = spf.newSAXParser(); + final XMLReader xmlReader = saxParser.getXMLReader(); + + xmlReader.setContentHandler(handler); + xmlReader.parse(new InputSource(is)); + } + catch (final Exception e) { + Log.e(ES_GOB_AFIRMA,"Error al cargar el fichero XML de definicion de lote: " + e + //$NON-NLS-1$ + "\n" + new String(xml, DEFAULT_CHARSET)); //$NON-NLS-1$ + throw new IOException("Error al cargar el fichero XML de definicion de lote: " + e, e); //$NON-NLS-1$ + } + + final SignBatchConfig config = handler.getBatchConfig(); + + this.id = config.getId() != null ? config.getId() : UUID.randomUUID().toString(); + this.algorithm = config.getAlgorithm(); + this.concurrentTimeout = config.getConcurrentTimeout(); + this.stopOnError = config.isStopOnError(); + this.signs = config.getSingleSigns(); + + } +} diff --git a/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/SingleSignConstants.java b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/SingleSignConstants.java new file mode 100644 index 0000000..6ec0bcf --- /dev/null +++ b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/SingleSignConstants.java @@ -0,0 +1,65 @@ +/* Copyright (C) 2011 [Gobierno de Espana] + * This file is part of "Cliente @Firma". + * "Cliente @Firma" 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. + * - or The European Software License; either version 1.1 or (at your option) any later version. + * You may contact the copyright holder at: soporte.afirma@seap.minhap.es + * + * Este fichero es parte de Server triphase signer de ClienteFirma, + * ha sido adaptado y modificado para el funcionamiento de la firma + * batch en android. + * + * Este software sigue los mismos criterios de licencia previos a su + * modificación: https://github.com/ctt-gob-es/clienteafirma/ + * + * Modificado por: Eduardo García + * Fecha modificación: 10 sep 2020 + */ + +package es.gob.afirma.android.signers.batch; + +public class SingleSignConstants { + + /** Tipo de operación de firma. */ + public enum SignSubOperation { + + /** Firma. */ + SIGN("sign"), //$NON-NLS-1$ + + /** Cofirma. */ + COSIGN("cosign"), //$NON-NLS-1$ + + /** Contrafirma. */ + COUNTERSIGN("countersign"); //$NON-NLS-1$ + + private final String name; + + private SignSubOperation(final String n) { + this.name = n; + } + + @Override + public String toString() { + return this.name; + } + + /** Obtiene el tipo de operación de firma a partir de su nombre. + * @param name Nombre del tipo de operación de firma. + * @return Tipo de operación de firma. */ + public static SignSubOperation getSubOperation(final String name) { + if (SIGN.toString().equalsIgnoreCase(name)) { + return SIGN; + } + if (COSIGN.toString().equalsIgnoreCase(name)) { + return COSIGN; + } + if (COUNTERSIGN.toString().equalsIgnoreCase(name)) { + return COUNTERSIGN; + } + throw new IllegalArgumentException( + "Tipo de operacion (suboperation) de firma no soportado: " + name //$NON-NLS-1$ + ); + } + } +} diff --git a/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/signer/SignBatchXmlHandler.java b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/signer/SignBatchXmlHandler.java new file mode 100644 index 0000000..5ec60bf --- /dev/null +++ b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/signer/SignBatchXmlHandler.java @@ -0,0 +1,271 @@ +/* + * Este fichero es parte de Server triphase signer de ClienteFirma, + * ha sido adaptado y modificado para el funcionamiento de la firma + * batch en android. + * + * Este software sigue los mismos criterios de licencia previos a su + * modificación: https://github.com/ctt-gob-es/clienteafirma/ + * + * Modificado por: Eduardo García + * Fecha modificación: 10 sep 2020 + */ +package es.gob.afirma.android.signers.batch.signer; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import java.io.CharArrayWriter; +import java.io.IOException; +import java.util.Properties; + +import es.gob.afirma.android.signers.constants.SignAlgorithm; +import es.gob.afirma.android.signers.constants.SignFormat; +import es.gob.afirma.android.signers.batch.SingleSignConstants; +import es.gob.afirma.core.misc.AOUtil; +import es.gob.afirma.android.server.triphase.signer.SignBatchConfig; + +public class SignBatchXmlHandler extends DefaultHandler { + + private static final String NODE_NAME_SIGNBATCH = "signbatch"; //$NON-NLS-1$ + + private static final String NODE_NAME_SINGLESIGN = "singlesign"; //$NON-NLS-1$ + + private static final String NODE_NAME_DATASOURCE = "datasource"; //$NON-NLS-1$ + + private static final String NODE_NAME_FORMAT = "format"; //$NON-NLS-1$ + + private static final String NODE_NAME_SUBOPERATIONS = "suboperation"; //$NON-NLS-1$ + + private static final String NODE_NAME_EXTRAPARAMS = "extraparams"; //$NON-NLS-1$ + + private static final String NODE_NAME_SIGNSAVER = "signsaver"; //$NON-NLS-1$ + + private static final String NODE_NAME_CLASS = "class"; //$NON-NLS-1$ + + private static final String NODE_NAME_CONFIG = "config"; //$NON-NLS-1$ + + private static final String ATTR_ID = "Id"; //$NON-NLS-1$ + + private static final String ATTR_STOPONERROR = "stoponerror"; //$NON-NLS-1$ + + private static final String ATTR_ALGORITHM = "algorithm"; //$NON-NLS-1$ + + private static final String ATTR_CONCURRENT_TIMEOUT = "concurrenttimeout"; //$NON-NLS-1$ + + private static final int UNDEFINED = 0; + + private static final int ELEMENT_SIGNBATCH = 1; + private static final int ELEMENT_SINGLESIGN = 2; + private static final int ELEMENT_DATASOURCE = 3; + private static final int ELEMENT_FORMAT = 4; + private static final int ELEMENT_SUBOPERATION = 5; + private static final int ELEMENT_EXTRAPARAMS = 6; + private static final int ELEMENT_CLASS = 7; + private static final int ELEMENT_SIGNSAVER = 8; + private static final int ELEMENT_CONFIG = 9; + + private int parent; + + private SignBatchConfig batchConfig; + + private SingleSign currentSign; + + private int currentElement; + + private SignSaver currentSignSaver; + + private CharArrayWriter acumulateContent; + + @Override + public void startDocument() throws SAXException { + this.batchConfig = new SignBatchConfig(); + this.parent = UNDEFINED; + this.currentElement = UNDEFINED; + this.currentSignSaver = null; + this.acumulateContent = new CharArrayWriter(); + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + switch (localName) { + case NODE_NAME_SIGNBATCH: + if (this.parent != UNDEFINED) { + throw new SAXException(String.format("El nodo %s debe ser el primero del XML", NODE_NAME_SIGNBATCH)); //$NON-NLS-1$ + } + this.parent = ELEMENT_SIGNBATCH; + this.currentElement = ELEMENT_SIGNBATCH; + + // Algoritmo de firma (Obligatorio) + if (attributes != null && attributes.getValue(ATTR_ALGORITHM) != null) { + this.batchConfig.setAlgorithm(SignAlgorithm.getAlgorithm(attributes.getValue(ATTR_ALGORITHM))); + } + else { + throw new SAXException(String.format("El nodo %s debe contener al menos el atributo de algoritmo", NODE_NAME_SIGNBATCH)); //$NON-NLS-1$ + } + // Para en caso de error (Opcional). Por defecto, true + if (attributes.getValue(ATTR_STOPONERROR) != null) { + this.batchConfig.setStopOnError(Boolean.parseBoolean(attributes.getValue(ATTR_STOPONERROR))); + } + // Tiempo de espera (Opcional). Por defecto, 0 + if (attributes.getValue(ATTR_CONCURRENT_TIMEOUT) != null) { + try { + this.batchConfig.setConcurrentTimeout(Long.parseLong(attributes.getValue(ATTR_CONCURRENT_TIMEOUT))); + } + catch (final Exception e) { + throw new SAXException("Se ha establecido un valor invalido para la espera maxima", e); //$NON-NLS-1$ + } + } + // Identificador de lote (Opcional) + if (attributes.getValue(ATTR_ID) != null) { + this.batchConfig.setId(attributes.getValue(ATTR_ID)); + } + break; + case NODE_NAME_SINGLESIGN: + if (this.parent != ELEMENT_SIGNBATCH) { + throw new SAXException(String.format("El nodo %s debe estar contenido en un nodo %s", NODE_NAME_SINGLESIGN, NODE_NAME_SIGNBATCH)); //$NON-NLS-1$ + } + this.parent = ELEMENT_SINGLESIGN; + this.currentElement = ELEMENT_SINGLESIGN; + if (attributes == null || attributes.getValue(ATTR_ID) == null) { + throw new SAXException(String.format("No se ha indicar el atributo %s de una de las firmas", ATTR_ID)); //$NON-NLS-1$ + } + this.currentSign = new SingleSign(attributes.getValue(ATTR_ID)); + break; + case NODE_NAME_DATASOURCE: + if (this.parent != ELEMENT_SINGLESIGN) { + throw new SAXException(String.format("El nodo %s debe estar contenido en un nodo %s", NODE_NAME_DATASOURCE, NODE_NAME_SINGLESIGN)); //$NON-NLS-1$ + } + this.currentElement = ELEMENT_DATASOURCE; + break; + case NODE_NAME_FORMAT: + if (this.parent != ELEMENT_SINGLESIGN) { + throw new SAXException(String.format("El nodo %s debe estar contenido en un nodo %s", NODE_NAME_FORMAT, NODE_NAME_SINGLESIGN)); //$NON-NLS-1$ + } + this.currentElement = ELEMENT_FORMAT; + break; + case NODE_NAME_SUBOPERATIONS: + if (this.parent != ELEMENT_SINGLESIGN) { + throw new SAXException(String.format("El nodo %s debe estar contenido en un nodo %s", NODE_NAME_SUBOPERATIONS, NODE_NAME_SINGLESIGN)); //$NON-NLS-1$ + } + this.currentElement = ELEMENT_SUBOPERATION; + break; + case NODE_NAME_EXTRAPARAMS: + if (this.parent != ELEMENT_SINGLESIGN) { + throw new SAXException(String.format("El nodo %s debe estar contenido en un nodo %s", NODE_NAME_EXTRAPARAMS, NODE_NAME_SINGLESIGN)); //$NON-NLS-1$ + } + this.currentElement = ELEMENT_EXTRAPARAMS; + break; + case NODE_NAME_SIGNSAVER: + if (this.parent != ELEMENT_SINGLESIGN) { + throw new SAXException(String.format("El nodo %s debe estar contenido en un nodo %s", NODE_NAME_SIGNSAVER, NODE_NAME_SINGLESIGN)); //$NON-NLS-1$ + } + this.parent = ELEMENT_SIGNSAVER; + this.currentElement = ELEMENT_SIGNSAVER; + break; + case NODE_NAME_CLASS: + if (this.parent != ELEMENT_SIGNSAVER) { + throw new SAXException(String.format("El nodo %s debe estar contenido en un nodo %s", NODE_NAME_CLASS, NODE_NAME_CONFIG)); //$NON-NLS-1$ + } + this.currentElement = ELEMENT_CLASS; + break; + case NODE_NAME_CONFIG: + if (this.parent != ELEMENT_SIGNSAVER) { + throw new SAXException(String.format("El nodo %s debe estar contenido en un nodo %s", NODE_NAME_CONFIG, NODE_NAME_CONFIG)); //$NON-NLS-1$ + } + this.currentElement = ELEMENT_CONFIG; + break; + + default: + throw new SAXException("Nodo no reconocido: " + localName); //$NON-NLS-1$ + } + } + + @Override + public void characters(final char ch[], final int start, final int length) throws SAXException { + this.acumulateContent.write(ch, start, length); + } + + @Override + public void endElement(final String uri, final String localName, final String qName) throws SAXException { + + // Realizamos la accion concreta para el elemento actual. Sin embargo, esta variable + // solo tendra el valor adecuado cuando el nodo no contuviese otro dentro. En caso + // contrario, estaria como indefinido. Asi pues, los nodos indefinidos tendran que + // tratarse segun el nombre del elemento. + switch (this.currentElement) { + case UNDEFINED: + switch (localName) { + case NODE_NAME_SIGNBATCH: + this.parent = UNDEFINED; + break; + case NODE_NAME_SINGLESIGN: + this.parent = ELEMENT_SIGNBATCH; + if (this.currentSignSaver == null || !this.currentSignSaver.isInitialized()) { + throw new SAXException("La clase de guardado no se ha inicializado para la firma " + this.currentSign.getId()); //$NON-NLS-1$ + } + this.batchConfig.addSingleSign(this.currentSign); + this.currentSign = null; + this.currentSignSaver = null; + break; + case NODE_NAME_SIGNSAVER: + this.parent = ELEMENT_SINGLESIGN; + break; + default: + break; + } + break; + case ELEMENT_DATASOURCE: + this.currentSign.setDataSource(new String(this.acumulateContent.toCharArray()).trim()); + break; + case ELEMENT_FORMAT: + this.currentSign.setFormat(SignFormat.getFormat(new String(this.acumulateContent.toCharArray()).trim())); + break; + case ELEMENT_SUBOPERATION: + this.currentSign.setSubOperation(SingleSignConstants.SignSubOperation.getSubOperation(new String(this.acumulateContent.toCharArray()).trim())); + break; + case ELEMENT_EXTRAPARAMS: + Properties extraParams = new Properties(); + if (this.acumulateContent.size() > 0) { + try { + extraParams = AOUtil.base642Properties(new String(this.acumulateContent.toCharArray()).trim()); + } catch (final IOException e) { + throw new SAXException("ExtraParams mal codificados en la firma " + this.currentSign.getId(), e); //$NON-NLS-1$ + } + } + this.currentSign.setExtraParams(extraParams); + break; + case ELEMENT_CLASS: + try { + this.currentSignSaver = (SignSaver) Class.forName(new String(this.acumulateContent.toCharArray()).trim()).newInstance(); + } catch (final Exception e) { + throw new SAXException("No se pudo cargar la clase de guardado para la firma " + this.currentSign.getId(), e); //$NON-NLS-1$ + } + this.currentSign.setSignSaver(this.currentSignSaver); + break; + case ELEMENT_CONFIG: + if (this.currentSignSaver == null) { + throw new SAXException("No se definio la clase de guardado para la firma " + this.currentSign.getId()); //$NON-NLS-1$ + } + Properties config = new Properties(); + if (this.acumulateContent.size() > 0) { + try { + config = AOUtil.base642Properties(new String(this.acumulateContent.toCharArray()).trim()); + } catch (final IOException e) { + throw new SAXException("La configuracion de la clase de guardado esta mal codificada en la firma " + this.currentSign.getId(), e); //$NON-NLS-1$ + } + } + this.currentSignSaver.init(config); + break; + default: + break; + } + this.currentElement = UNDEFINED; + this.acumulateContent.reset(); + } + + public SignBatchConfig getBatchConfig() { + return this.batchConfig; + } +} \ No newline at end of file diff --git a/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/signer/SignSaver.java b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/signer/SignSaver.java new file mode 100644 index 0000000..3e48884 --- /dev/null +++ b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/signer/SignSaver.java @@ -0,0 +1,51 @@ +/* Copyright (C) 2011 [Gobierno de Espana] + * This file is part of "Cliente @Firma". + * "Cliente @Firma" 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. + * - or The European Software License; either version 1.1 or (at your option) any later version. + * You may contact the copyright holder at: soporte.afirma@seap.minhap.es + * + * Este fichero es parte de Server triphase signer de ClienteFirma, + * ha sido adaptado y modificado para el funcionamiento de la firma + * batch en android. + * + * Modificado por: Eduardo García + * Fecha modificación: 10 sep 2020 + */ + +package es.gob.afirma.android.signers.batch.signer; + +import java.io.IOException; +import java.util.Properties; + +/** Interfaz para el guardado, almacenaje o envío de firmas una vez realizadas. + * @author Tomás García-Merás. */ +public interface SignSaver { + + /** Guarda una firma electrónica. + * @param sign Definición de la firma que se hizo. + * @param dataToSave Datos a guardar, resultado de la firma electrónica. + * @throws IOException Si hay problemas durante el proceso. */ + void saveSign(final SingleSign sign, final byte[] dataToSave) throws IOException; + + /** Deshace un guardado previo (para los modos transaccionales). + * @param sign Identificador de la firma a deshacer. */ + void rollback(final SingleSign sign); + + /** Configura cómo ha de guardarse la firma electrónica. + * cada implementación requerirá unas propiedades distintas dentro del + * objeto de propiedades. + * @param config Propiedades de configuración. */ + void init(final Properties config); + + /** Obtiene las propiedades de configuración. + * @return Propiedades de configuración. */ + Properties getConfig(); + + /** Indica si el manejador está inicializado. + * @return {@code true} si el manejador está inicializado, + * {@code false} en caso contrario. + */ + boolean isInitialized(); +} diff --git a/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/signer/SingleSign.java b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/signer/SingleSign.java new file mode 100644 index 0000000..fdb050c --- /dev/null +++ b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/signer/SingleSign.java @@ -0,0 +1,543 @@ +/* Copyright (C) 2011 [Gobierno de Espana] + * This file is part of "Cliente @Firma". + * "Cliente @Firma" 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. + * - or The European Software License; either version 1.1 or (at your option) any later version. + * You may contact the copyright holder at: soporte.afirma@seap.minhap.es + * + * Este fichero es parte de Server triphase signer de ClienteFirma, + * ha sido adaptado y modificado para el funcionamiento de la firma + * batch en android. + * + * Modificado por: Eduardo García + * Fecha modificación: 10 sep 2020 + */ +package es.gob.afirma.android.signers.batch.signer; + +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Properties; +import java.util.UUID; +import java.util.logging.Logger; + +import es.gob.afirma.android.signers.constants.SignFormat; +import es.gob.afirma.android.signers.batch.SingleSignConstants; +import es.gob.afirma.core.AOException; +import es.gob.afirma.core.misc.AOUtil; +import es.gob.afirma.core.misc.Base64; +import es.gob.afirma.android.signers.batch.signer.SingleSign.ProcessResult.Result; +import es.gob.afirma.android.signers.batch.SingleSignConstants.SignSubOperation; + +/** Firma electrónica única dentro de un lote. + * @author Tomás García-Merás. */ +public final class SingleSign { + + private static final String PROP_ID = "SignatureId"; //$NON-NLS-1$ + + private static final String XML_ATTRIBUTE_ID = "Id"; //$NON-NLS-1$ + + private static final String XML_ELEMENT_DATASOURCE = "datasource"; //$NON-NLS-1$ + private static final String XML_ELEMENT_FORMAT = "format"; //$NON-NLS-1$ + private static final String XML_ELEMENT_SUBOPERATION = "suboperation"; //$NON-NLS-1$ + private static final String XML_ELEMENT_SIGNSAVER = "signsaver"; //$NON-NLS-1$ + private static final String XML_ELEMENT_SIGNSAVER_CLASSNAME = "class"; //$NON-NLS-1$ + private static final String XML_ELEMENT_SIGNSAVER_CONFIG = "config"; //$NON-NLS-1$ + private static final String XML_ELEMENT_EXTRAPARAMS = "extraparams"; //$NON-NLS-1$ + + private static final String HTTP_SCHEME = "http://"; //$NON-NLS-1$ + private static final String HTTPS_SCHEME = "https://"; //$NON-NLS-1$ + private static final String FTP_SCHEME = "ftp://"; //$NON-NLS-1$ + + private static final Logger LOGGER = Logger.getLogger("es.gob.afirma"); //$NON-NLS-1$ + + private Properties extraParams; + + private String dataSource; + + private SignFormat format; + + private final String id; + + private SingleSignConstants.SignSubOperation subOperation; + + private SignSaver signSaver; + + private ProcessResult processResult = new ProcessResult(Result.NOT_STARTED, null); + + /** Crea una definición de tarea de firma electrónica única. + * @param id Identificador de la firma. */ + SingleSign(final String id) { + this.id = id; + this.extraParams = new Properties(); + // El identificador de la firma debe transmitirse al firmador trifasico a traves + // de los extraParams para que este lo utilice y asi podamos luego asociar la + // firma con los datos a los que corresponden + this.extraParams.put(PROP_ID, getId()); + } + + /** Crea una definición de tarea de firma electrónica única. + * @param id Identificador de la firma. + * @param dataSrc Datos a firmar. + * @param fmt Formato de firma. + * @param subOp Tipo de firma a realizar. + * @param xParams Opciones adicionales de la firma. + * @param ss Objeto para guardar la firma una vez completada. */ + public SingleSign(final String id, + final String dataSrc, + final SignFormat fmt, + final SignSubOperation subOp, + final Properties xParams, + final SignSaver ss) { + + if (dataSrc == null) { + throw new IllegalArgumentException( + "El origen de los datos a firmar no puede ser nulo" //$NON-NLS-1$ + ); + } + + if (fmt == null) { + throw new IllegalArgumentException( + "El formato de firma no puede ser nulo" //$NON-NLS-1$ + ); + } + + if (ss == null) { + throw new IllegalArgumentException( + "El objeto de guardado de firma no puede ser nulo" //$NON-NLS-1$ + ); + } + + this.dataSource = dataSrc; + this.format = fmt; + + this.id = id != null ? id : UUID.randomUUID().toString(); + + // El identificador de la firma debe transmitirse al firmador trifasico a traves + // de los extraParams para que este lo utilice y asi podamos luego asociar la + // firma con los datos a los que corresponden + this.extraParams = xParams != null ? xParams : new Properties(); + this.extraParams.put(PROP_ID, getId()); + + this.subOperation = subOp; + this.signSaver = ss; + } + + void save(final byte[] dataToSave) throws IOException { + this.signSaver.saveSign(this, dataToSave); + } + + public String getDataSource() { + return dataSource; + } + + /** + * Recupera los parámetros de configuración del formato de firma. + * @return Configuración del formato de firma. + */ + public Properties getExtraParams() { + return this.extraParams; + } + + /** + * Recupera el formato de firma. + * @return Formato de firma. + */ + public SignFormat getSignFormat() { + return this.format; + } + + public String getRetrieveServerUrl() { + return this.signSaver.getConfig().getProperty("URL"); + } + + public String getSignId() { + String signId = this.signSaver.getConfig().getProperty("fileId"); + return signId != null && !signId.isEmpty() ? signId : getId(); + } + + public SignSubOperation getSubOperation() { + return this.subOperation; + } + + void setExtraParams(final Properties extraParams) { + // El identificador de la firma debe transmitirse al firmador trifasico a traves + // de los extraParams para que este lo utilice y asi podamos luego asociar la + // firma con los datos a los que corresponden + this.extraParams = extraParams != null ? extraParams : new Properties(); + this.extraParams.put(PROP_ID, getId()); + } + + void setDataSource(final String dataSource) { + this.dataSource = dataSource; + } + + void setFormat(final SignFormat format) { + this.format = format; + } + + void setSubOperation(final SignSubOperation subOperation) { + this.subOperation = subOperation; + } + + void setSignSaver(final SignSaver signSaver) { + this.signSaver = signSaver; + } + +// private static void checkDataSource(final String dataSource) { +// if (dataSource == null) { +// throw new IllegalArgumentException( +// "El origen de los datos no puede ser nulo" //$NON-NLS-1$ +// ); +// } +// for (final String allowed : BatchConfigManager.getAllowedSources()) { +// if ("base64".equalsIgnoreCase(allowed) && Base64.isBase64(dataSource)) { //$NON-NLS-1$ +// return; +// } +// if (allowed.endsWith("*")) { //$NON-NLS-1$ +// if (dataSource.startsWith(allowed.replace("*", ""))) { //$NON-NLS-1$ //$NON-NLS-2$ +// return; +// } +// } +// else { +// if (dataSource.equals(allowed)) { +// return; +// } +// } +// } +// throw new SecurityException("Origen de datos no valido"); //$NON-NLS-1$ +// } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(" \n <"); //$NON-NLS-1$ + sb.append(XML_ELEMENT_DATASOURCE); + sb.append(">"); //$NON-NLS-1$ + sb.append(this.dataSource); + sb.append("\n <"); //$NON-NLS-1$ + sb.append(XML_ELEMENT_FORMAT); + sb.append(">"); //$NON-NLS-1$ + sb.append(getSignFormat().toString()); + sb.append("\n <"); //$NON-NLS-1$ + sb.append(XML_ELEMENT_SUBOPERATION); + sb.append(">"); //$NON-NLS-1$ + sb.append(getSubOperation().toString()); + sb.append("\n <"); //$NON-NLS-1$ + sb.append(XML_ELEMENT_EXTRAPARAMS); + sb.append(">"); //$NON-NLS-1$ + try { + sb.append(AOUtil.properties2Base64(getExtraParams())); + } + catch (final IOException e) { + LOGGER.severe( + "Error convirtiendo los parametros adicionales de la firma '" + getId() + "' a Base64: " + e //$NON-NLS-1$ //$NON-NLS-2$ + ); + } + sb.append("\n <"); //$NON-NLS-1$ + sb.append(XML_ELEMENT_SIGNSAVER); + sb.append(">\n <"); //$NON-NLS-1$ + sb.append(XML_ELEMENT_SIGNSAVER_CLASSNAME); + sb.append(">"); //$NON-NLS-1$ + sb.append(this.signSaver.getClass().getName()); + sb.append("\n <"); //$NON-NLS-1$ + sb.append(XML_ELEMENT_SIGNSAVER_CONFIG); + sb.append(">"); //$NON-NLS-1$ + try { + sb.append(AOUtil.properties2Base64(this.signSaver.getConfig())); + } + catch (final IOException e) { + LOGGER.severe( + "Error convirtiendo la configuracion del objeto de guardado de la firma '" + getId() + "' a Base64: " + e //$NON-NLS-1$ //$NON-NLS-2$ + ); + } + sb.append("\n \n "); //$NON-NLS-1$ + + return sb.toString(); + } + + /** Realiza el proceso de prefirma, incluyendo la descarga u obtención de datos. + * @param certChain Cadena de certificados del firmante. + * @param algorithm Algoritmo de firma. + * @return Nodo firma del XML de datos trifásicos (sin ninguna etiqueta + * antes ni después). + * @throws AOException Si hay problemas en la propia firma electrónica. + * @throws IOException Si hay problemas en la obtención, tratamiento o gradado de datos. */ +// String doPreProcess(final X509Certificate[] certChain, +// final SignAlgorithm algorithm) throws IOException, +// AOException { +// return SingleSignPreProcessor.doPreProcess(this, certChain, algorithm); +// } + + /** Obtiene la tarea de preproceso de firma para ser ejecutada en paralelo. + * @param certChain Cadena de certificados del firmante. + * @param algorithm Algoritmo de firma. + * @return Tarea de preproceso de firma para ser ejecutada en paralelo. */ +// Callable getPreProcessCallable(final X509Certificate[] certChain, +// final SingleSignConstants.SignAlgorithm algorithm) { +// return new Callable() { +// @Override +// public String call() throws IOException, AOException { +// return doPreProcess(certChain, algorithm); +// } +// }; +// } + + /** Realiza el proceso de postfirma, incluyendo la subida o guardado de datos. + * @param certChain Cadena de certificados del firmante. + * @param td Datos trifásicos relativos únicamente a esta firma. + * Debe serializarse como un XML con esta forma (ejemplo): + *
+     *            <xml>
+     *             <firmas>
+     *              <firma Id="53820fb4-336a-47ee-b7ba-f32f58e5cfd6">
+     *               <param n="PRE">MYICXDAYBgk[...]GvykA=</param>
+     *               <param n="PK1">dC2dIILB9HV[...]xT1bY=</param>
+     *               <param n="NEED_PRE">true</param>
+     *              </firma>
+     *             </firmas>
+     *            </xml>
+     *           
+ * @param algorithm Algoritmo de firma. + * @param batchId Identificador del lote de firma. + * @throws AOException Si hay problemas en la propia firma electrónica. + * @throws IOException Si hay problemas en la obtención, tratamiento o gradado de datos. + * @throws NoSuchAlgorithmException Si no se soporta algún algoritmo necesario. */ +// void doPostProcess(final X509Certificate[] certChain, +// final TriphaseData td, +// final SingleSignConstants.SignAlgorithm algorithm, +// final String batchId) throws IOException, +// AOException, +// NoSuchAlgorithmException { +// SingleSignPostProcessor.doPostProcess( +// this, certChain, td, algorithm, batchId +// ); +// } + + /** Obtiene la tarea de postproceso de firma para ser ejecutada en paralelo. + * @param certChain Cadena de certificados del firmante. + * @param td Datos trifásicos relativos únicamente a esta firma. + * Debe serializarse como un XML con esta forma (ejemplo): + *
+     *            <xml>
+     *             <firmas>
+     *              <firma Id="53820fb4-336a-47ee-b7ba-f32f58e5cfd6">
+     *               <param n="PRE">MYICXDAYBgk[...]GvykA=</param>
+     *               <param n="PK1">dC2dIILB9HV[...]xT1bY=</param>
+     *               <param n="NEED_PRE">true</param>
+     *              </firma>
+     *             </firmas>
+     *            </xml>
+     *           
+ * @param algorithm Algoritmo de firma. + * @param batchId Identificador del lote de firma. + * @return Tarea de postproceso de firma para ser ejecutada en paralelo. */ +// Callable getPostProcessCallable(final X509Certificate[] certChain, +// final TriphaseData td, +// final SingleSignConstants.SignAlgorithm algorithm, +// final String batchId) { +// return new Callable() { +// @Override +// public CallableResult call() { +// try { +// doPostProcess(certChain, td, algorithm, batchId); +// } +// catch(final Exception e) { +// return new CallableResult(getId(), e); +// } +// return new CallableResult(getId()); +// } +// }; +// +// } + +// Callable getSaveCallable(final TempStore ts, final String batchId) { +// return new Callable() { +// @Override +// public CallableResult call() { +// try { +// save(ts.retrieve(SingleSign.this, batchId)); +// } +// catch(final Exception e) { +// return new CallableResult(getId(), e); +// } +// return new CallableResult(getId()); +// } +// }; +// } + + /** + * Recupera el identificador asignado en el lote a la firma. + * @return Identificador. + */ + public String getId() { + return this.id; + } + +// /** +// * Recupera los datos que se deben procesar. +// * @param stored {@code} true, indica que en caso de tratarse de datos remotos, estos ya +// * estarán cargados en un temporal y deben tomarse de este; {@code false} indica +// * que se deberán cargar los datos desde la fuente y, en caso de ser remotos, se +// * creará un temporal para ellos. +// * @return Datos. +// * @throws IOException Cuando no se pueden obtener los datos en caso de que estos sean remotos. +// */ +// public byte[] getData(final boolean stored) throws IOException { +// // Si se nos solicita un fichero remoto, calculamos cual seria el fichero +// // temporal que le corresponderia +// String tempResource = null; +// if (this.dataSource.startsWith(HTTP_SCHEME) || this.dataSource.startsWith(HTTPS_SCHEME) || this.dataSource.startsWith(FTP_SCHEME)) { +// try { +// tempResource = getTempFileName(this.dataSource, this.id); +// } +// catch (final Exception e) { +// LOGGER.warning("No se puede calcular el nombre de un temporal para un recurso remoto: " + e); //$NON-NLS-1$ +// tempResource = null; +// } +// } +// +// // Si se indica que este fichero ya se almaceno +// // y deberia haber un recurso local, lo cargamos +// byte[] data = null; +// if (stored && tempResource != null) { +// try { +// final TempStore tempStore = TempStoreFactory.getTempStore(); +// data = tempStore.retrieve(tempResource); +// tempStore.delete(tempResource); +// } +// catch (final Exception e) { +// LOGGER.warning(String.format("No se puede recuperar el recurso temporal %0s, se cargara de la fuente original: " + e, tempResource)); //$NON-NLS-1$ +// } +// } +// +// // Si no, lo descargamos de la fuente original +// if (data == null) { +// checkDataSource(this.dataSource); +// data = DataDownloader.downloadData(this.dataSource); +// } +// +// // Finalmente, si se habia indicado que no habia recurso temporal +// // pero deberia haberlo, lo creamos +// if (!stored && tempResource != null) { +// TempStoreFactory.getTempStore().store(data, tempResource); +// } +// +// return data; +// } + + private static String getTempFileName(final String source, final String signId) throws NoSuchAlgorithmException { + return Base64.encode(MessageDigest.getInstance("SHA-1").digest((source + signId).getBytes()), true); //$NON-NLS-1$ + } + + void setProcessResult(final ProcessResult pResult) { + this.processResult = pResult; + } + + ProcessResult getProcessResult() { + this.processResult.setId(getId()); + return this.processResult; + } + + void rollbackSave() { + this.signSaver.rollback(this); + } + + static class CallableResult { + + private final String signId; + private final Exception exception; + + CallableResult(final String id) { + this.signId = id; + this.exception = null; + } + + CallableResult(final String id, final Exception e) { + this.signId = id; + this.exception = e; + } + + boolean isOk() { + return this.exception == null; + } + + Exception getError() { + return this.exception; + } + + String getSignatureId() { + return this.signId; + } + } + + static final class ProcessResult { + + enum Result { + NOT_STARTED, + DONE_AND_SAVED, + DONE_BUT_NOT_SAVED_YET, + DONE_BUT_SAVED_SKIPPED, + DONE_BUT_ERROR_SAVING, + ERROR_PRE, + ERROR_POST, + SKIPPED, + SAVE_ROLLBACKED; + } + + private final Result result; + private final String description; + private String signId; + + boolean wasSaved() { + return Result.DONE_AND_SAVED.equals(this.result); + } + + static final ProcessResult PROCESS_RESULT_OK_UNSAVED = new ProcessResult(Result.DONE_BUT_NOT_SAVED_YET, null); + static final ProcessResult PROCESS_RESULT_SKIPPED = new ProcessResult(Result.SKIPPED, null); + static final ProcessResult PROCESS_RESULT_DONE_SAVED = new ProcessResult(Result.DONE_AND_SAVED, null); + static final ProcessResult PROCESS_RESULT_ROLLBACKED = new ProcessResult(Result.SAVE_ROLLBACKED, null); + + ProcessResult(final Result r, final String d) { + if (r == null) { + throw new IllegalArgumentException( + "El resultado no puede ser nulo" //$NON-NLS-1$ + ); + } + this.result = r; + this.description = d != null ? d : ""; //$NON-NLS-1$ + } + + @Override + public String toString() { + return ""; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } + + void setId(final String id) { + this.signId = id; + } + + public Result getResult() { + return this.result; + } + } + +} diff --git a/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/constants/SignAlgorithm.java b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/constants/SignAlgorithm.java new file mode 100644 index 0000000..265031e --- /dev/null +++ b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/constants/SignAlgorithm.java @@ -0,0 +1,69 @@ +/* Copyright (C) 2011 [Gobierno de Espana] + * This file is part of "Cliente @Firma". + * "Cliente @Firma" 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. + * - or The European Software License; either version 1.1 or (at your option) any later version. + * You may contact the copyright holder at: soporte.afirma@seap.minhap.es + * + * Este fichero es parte de Server triphase signer de ClienteFirma, + * ha sido adaptado y modificado a partir de la clase SingleSignConstants + * para el funcionamiento de la firma batch en android. + * + * Este software sigue los mismos criterios de licencia previos a su + * modificación: https://github.com/ctt-gob-es/clienteafirma/ + * + * Modificado por: Eduardo García + * Fecha modificación: 10 sep 2020 + */ + +package es.gob.afirma.android.signers.constants; + +import es.gob.afirma.core.signers.AOSignConstants; + +public enum SignAlgorithm { + + /** SHA1withRSA. */ + SHA1WITHRSA(AOSignConstants.SIGN_ALGORITHM_SHA1WITHRSA), + + /** SHA256withRSA. */ + SHA256WITHRSA(AOSignConstants.SIGN_ALGORITHM_SHA256WITHRSA), + + /** SHA284withRSA. */ + SHA384WITHRSA(AOSignConstants.SIGN_ALGORITHM_SHA384WITHRSA), + + /** SHA512withRSA. */ + SHA512WITHRSA(AOSignConstants.SIGN_ALGORITHM_SHA512WITHRSA); + + private final String name; + + private SignAlgorithm(final String n) { + this.name = n; + } + + @Override + public String toString() { + return this.name; + } + + /** Obtiene el algoritmo de firma a partir de su nombre. + * @param name Nombre del algoritmo de firma. + * @return Algoritmo firma. */ + public static SignAlgorithm getAlgorithm(final String name) { + if (SHA1WITHRSA.toString().equalsIgnoreCase(name)) { + return SHA1WITHRSA; + } + if (SHA256WITHRSA.toString().equalsIgnoreCase(name)) { + return SHA256WITHRSA; + } + if (SHA384WITHRSA.toString().equalsIgnoreCase(name)) { + return SHA384WITHRSA; + } + if (SHA512WITHRSA.toString().equalsIgnoreCase(name)) { + return SHA512WITHRSA; + } + throw new IllegalArgumentException( + "Tipo de algoritmo de firma no soportado: " + name //$NON-NLS-1$ + ); + } +} diff --git a/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/constants/SignFormat.java b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/constants/SignFormat.java new file mode 100644 index 0000000..82eafd6 --- /dev/null +++ b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/constants/SignFormat.java @@ -0,0 +1,81 @@ +/* Copyright (C) 2011 [Gobierno de Espana] + * This file is part of "Cliente @Firma". + * "Cliente @Firma" 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. + * - or The European Software License; either version 1.1 or (at your option) any later version. + * You may contact the copyright holder at: soporte.afirma@seap.minhap.es + * + * Este fichero es parte de Server triphase signer de ClienteFirma, + * ha sido adaptado y modificado a partir de la clase SingleSignConstants + * para el funcionamiento de la firma batch en android. + * + * Este software sigue los mismos criterios de licencia previos a su + * modificación: https://github.com/ctt-gob-es/clienteafirma/ + * + * Modificado por: Eduardo García + * Fecha modificación: 10 sep 2020 + */ + +package es.gob.afirma.android.signers.constants; + +import es.gob.afirma.core.signers.AOSignConstants; + +/** Formato de firma. */ +public enum SignFormat { + + /** CAdES. */ + CADES(AOSignConstants.SIGN_FORMAT_CADES), + + /** CAdES ASiC. */ + CADES_ASIC(AOSignConstants.SIGN_FORMAT_CADES_ASIC_S), + + /** XAdES. */ + XADES(AOSignConstants.SIGN_FORMAT_XADES), + + /** XAdES ASiC. */ + XADES_ASIC(AOSignConstants.SIGN_FORMAT_XADES_ASIC_S), + + /** PAdES. */ + PADES(AOSignConstants.SIGN_FORMAT_PADES), + + /** FacturaE. */ + FACTURAE(AOSignConstants.SIGN_FORMAT_FACTURAE), + + /** PKCS#1. */ + PKCS1(AOSignConstants.SIGN_FORMAT_PKCS1); + + private final String name; + + private SignFormat(final String n) { + this.name = n; + } + + @Override + public String toString() { + return this.name; + } + + /** Obtiene el formato de firma a partir de su nombre. + * @param name Nombre del formato de firma. + * @return Formato firma. */ + public static SignFormat getFormat(final String name) { + if (name != null) { + if (CADES.toString().equalsIgnoreCase(name.trim())) { + return CADES; + } + if (XADES.toString().equalsIgnoreCase(name.trim())) { + return XADES; + } + if (PADES.toString().equalsIgnoreCase(name.trim())) { + return PADES; + } + if (FACTURAE.toString().equalsIgnoreCase(name.trim())) { + return FACTURAE; + } + } + throw new IllegalArgumentException( + "Tipo de formato de firma no soportado: " + name //$NON-NLS-1$ + ); + } +} diff --git a/afirma-ui-android/app/src/main/java/es/gob/afirma/signers/batch/BatchException.java b/afirma-ui-android/app/src/main/java/es/gob/afirma/signers/batch/BatchException.java new file mode 100644 index 0000000..94e1a46 --- /dev/null +++ b/afirma-ui-android/app/src/main/java/es/gob/afirma/signers/batch/BatchException.java @@ -0,0 +1,9 @@ +package es.gob.afirma.signers.batch; + +public class BatchException extends Exception { + private static final long serialVersionUID = 1L; + + public BatchException(final String msg, final Throwable e) { + super(msg, e); + } +} diff --git a/afirma-ui-android/app/src/main/java/es/gob/afirma/signers/batch/SignSaverHttpPost.java b/afirma-ui-android/app/src/main/java/es/gob/afirma/signers/batch/SignSaverHttpPost.java new file mode 100644 index 0000000..783f946 --- /dev/null +++ b/afirma-ui-android/app/src/main/java/es/gob/afirma/signers/batch/SignSaverHttpPost.java @@ -0,0 +1,52 @@ +/* Copyright (C) 2011 [Gobierno de Espana] + * This file is part of "Cliente @Firma". + * "Cliente @Firma" 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. + * - or The European Software License; either version 1.1 or (at your option) any later version. + * Date: 10 sep 2020 + * You may contact the copyright holder at: soporte.afirma5@mpt.es + */ + +package es.gob.afirma.signers.batch; + +import java.io.IOException; +import java.util.Properties; + +import es.gob.afirma.android.signers.batch.signer.SignSaver; +import es.gob.afirma.android.signers.batch.signer.SingleSign; + +/** + * Implementación de SignSaverHttpPost para android. + */ +public class SignSaverHttpPost implements SignSaver { + private Properties config; + private boolean initialized = false; + + @Override + public void saveSign(SingleSign sign, byte[] dataToSave) throws IOException { + // No es necesario + throw new IOException("Esta operación no es válida"); + } + + @Override + public void rollback(SingleSign sign) { + // TODO + } + + @Override + public void init(Properties config) { + this.config = config; + this.initialized = true; + } + + @Override + public Properties getConfig() { + return this.config; + } + + @Override + public boolean isInitialized() { + return this.initialized; + } +} diff --git a/afirma-ui-android/app/src/main/res/values-en/strings.xml b/afirma-ui-android/app/src/main/res/values-en/strings.xml index c912688..8e95f61 100644 --- a/afirma-ui-android/app/src/main/res/values-en/strings.xml +++ b/afirma-ui-android/app/src/main/res/values-en/strings.xml @@ -75,4 +75,6 @@ Error while signing Success signature The selected file is too large and there is no enough memory to load it + Error occurred during signature processing + The process has been continued with %d errors \ No newline at end of file diff --git a/afirma-ui-android/app/src/main/res/values-fr/strings.xml b/afirma-ui-android/app/src/main/res/values-fr/strings.xml index 8fa3e10..61ba625 100644 --- a/afirma-ui-android/app/src/main/res/values-fr/strings.xml +++ b/afirma-ui-android/app/src/main/res/values-fr/strings.xml @@ -22,5 +22,6 @@ Erreur: Il n\'est pas possible la lecture du fichier bytes Le fichier est trop grande et il n\'y a pas de mémoire suffisant pour lui lire + Une erreur s\'est produite lors du traitement des signatures diff --git a/afirma-ui-android/app/src/main/res/values/strings.xml b/afirma-ui-android/app/src/main/res/values/strings.xml index 62b5827..bb58097 100644 --- a/afirma-ui-android/app/src/main/res/values/strings.xml +++ b/afirma-ui-android/app/src/main/res/values/strings.xml @@ -39,6 +39,7 @@ Guardar Cancelar Permitir + Ocurri\\u00F3 un error durante el prcesamiento de las firmas Ocurri\u00F3 un error al guardar los datos Ocurri\u00F3 un error al guardar la firma No se pudo transmitir la firma de los datos al navegador web @@ -89,5 +90,6 @@ Tarjeta no soportada Error al iniciar la conexi\u00F3n con la tarjeta. Int\u00E9ntelo de nuevo. MainActivityTest + Se ha continuado el proceso con %d errores \ No newline at end of file From bebc4f857afc2b21e716ca9fa58240a9146f0897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20L=2E=20Garc=C3=ADa=20Glez?= Date: Mon, 21 Sep 2020 15:56:54 +0100 Subject: [PATCH 2/2] =?UTF-8?q?Implementaci=C3=B3n=20de=20firma=20batch=20?= =?UTF-8?q?en=20base64=20y=20correcci=C3=B3n=20de=20errores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../afirma/android/WebBatchSignActivity.java | 33 ++++++----- .../signers/batch/signer/SingleSign.java | 57 +++++++++++-------- 2 files changed, 54 insertions(+), 36 deletions(-) diff --git a/afirma-ui-android/app/src/main/java/es/gob/afirma/android/WebBatchSignActivity.java b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/WebBatchSignActivity.java index 10ddfff..0fcc9e9 100644 --- a/afirma-ui-android/app/src/main/java/es/gob/afirma/android/WebBatchSignActivity.java +++ b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/WebBatchSignActivity.java @@ -214,16 +214,21 @@ private void getBatchFile() { private void signNext() { actualSign = batchReader.getSigns().remove(0); try { - Logger.i(ES_GOB_AFIRMA, "Se van a descargar los datos desde servidor con el identificador: " + this.actualSign.getId()); //$NON-NLS-1$ - - this.downloadFileTask = new DownloadFileTask( - this.actualSign.getId(), - new URL(this.actualSign.getDataSource()), - this.authMethod, - this - ); - this.downloadFileTask.execute(); - } catch (MalformedURLException e) { + actualSign.checkDatasource(); + if (actualSign.datasourceType.equals(SingleSign.DatasourceTypes.BASE64)) { + Logger.i(ES_GOB_AFIRMA, "Datasource en Base64 se omite la descarga"); + initSign(Base64.decode(actualSign.getDataSource().getBytes(), Base64.DEFAULT)); + } else { + Logger.i(ES_GOB_AFIRMA, "Se van a descargar los datos desde servidor con el identificador: " + this.actualSign.getId()); //$NON-NLS-1$ + this.downloadFileTask = new DownloadFileTask( + this.actualSign.getId(), + new URL(this.actualSign.getDataSource()), + this.authMethod, + this + ); + this.downloadFileTask.execute(); + } + } catch (MalformedURLException | IllegalArgumentException | SecurityException e) { Logger.e(ES_GOB_AFIRMA, "Error en la url de descarga: " + this.actualSign.getDataSource() + " id fichero: " + this.actualSign.getId()); } } @@ -233,7 +238,7 @@ private void setAuthMethod(Map params) { try { String auth = params.get("auth"); if (auth != null && !auth.isEmpty()) { - JSONObject authJson = new JSONObject(); + JSONObject authJson = new JSONObject(auth); this.authMethod.put(authJson.get("k"), authJson.get("v")); } } @@ -523,7 +528,8 @@ public void onSigningSuccess(final SignResult signature) { try { data = cipherKey != null ? CipherDataManager.cipherData(signature.getSignature(), - cipherKey) : Base64.encodeToString(signature.getSignature(), Base64.DEFAULT); + cipherKey) : Base64.encodeToString(signature.getSignature(), Base64.DEFAULT) + .replace("/","_").replace("+", "-"); } catch (final GeneralSecurityException e) { @@ -541,7 +547,8 @@ public void onSigningSuccess(final SignResult signature) { try { signingCert = cipherKey != null ? CipherDataManager.cipherData( signature.getSigningCertificate().getEncoded(), - this.parametersBatch.getDesKey()) : Base64.encodeToString(signature.getSigningCertificate().getEncoded(), Base64.DEFAULT); + this.parametersBatch.getDesKey()) : Base64.encodeToString(signature.getSigningCertificate().getEncoded(), Base64.DEFAULT) + .replace("/","_").replace("+", "-"); } catch (final GeneralSecurityException e) { Logger.e(ES_GOB_AFIRMA, "Error en el cifrado del certificado de firma: " + e, e); //$NON-NLS-1$ diff --git a/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/signer/SingleSign.java b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/signer/SingleSign.java index fdb050c..1e4b058 100644 --- a/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/signer/SingleSign.java +++ b/afirma-ui-android/app/src/main/java/es/gob/afirma/android/signers/batch/signer/SingleSign.java @@ -15,6 +15,8 @@ */ package es.gob.afirma.android.signers.batch.signer; +import android.provider.ContactsContract; + import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -54,6 +56,11 @@ public final class SingleSign { private Properties extraParams; + private static String allowedSources = "base64;http://*;https://*;ftp://*"; + public enum DatasourceTypes { + BASE64, HTTP, HTTPS, FTP + }; + public DatasourceTypes datasourceType; private String dataSource; private SignFormat format; @@ -185,29 +192,33 @@ void setSignSaver(final SignSaver signSaver) { this.signSaver = signSaver; } -// private static void checkDataSource(final String dataSource) { -// if (dataSource == null) { -// throw new IllegalArgumentException( -// "El origen de los datos no puede ser nulo" //$NON-NLS-1$ -// ); -// } -// for (final String allowed : BatchConfigManager.getAllowedSources()) { -// if ("base64".equalsIgnoreCase(allowed) && Base64.isBase64(dataSource)) { //$NON-NLS-1$ -// return; -// } -// if (allowed.endsWith("*")) { //$NON-NLS-1$ -// if (dataSource.startsWith(allowed.replace("*", ""))) { //$NON-NLS-1$ //$NON-NLS-2$ -// return; -// } -// } -// else { -// if (dataSource.equals(allowed)) { -// return; -// } -// } -// } -// throw new SecurityException("Origen de datos no valido"); //$NON-NLS-1$ -// } + public void checkDatasource() { + if (dataSource == null) { + throw new IllegalArgumentException( + "el origen de los datos no puede ser nulo" //$non-nls-1$ + ); + } + for (final String allowed : SingleSign.allowedSources.split(";")) { + if ("base64".equals(allowed) && Base64.isBase64(dataSource)) { //$non-nls-1$ + datasourceType = DatasourceTypes.BASE64; + return; + } + if (allowed.endsWith("*")) { //$non-nls-1$ + if (dataSource.startsWith(allowed.replace("*", ""))) { //$non-nls-1$ //$non-nls-2$ + for (DatasourceTypes t : DatasourceTypes.values()) + if (dataSource.startsWith(t.name().toLowerCase())) { + datasourceType = t; + return; + } + } + } else { + if (dataSource.equals(allowed)) { + return; + } + } + } + throw new SecurityException("origen de datos no valido"); //$non-nls-1$ + } @Override public String toString() {