He decidido comenzar un nuevo blog algo más dedicado al tema de la ciberseguridad y la privacidad así como vivencias y recuerdos “digitales”.
El nuevo blog es www.fernandogonzalez.es
He decidido comenzar un nuevo blog algo más dedicado al tema de la ciberseguridad y la privacidad así como vivencias y recuerdos “digitales”.
El nuevo blog es www.fernandogonzalez.es
Hace unos días, recibí un correo electrónico donde se me avisaba de un error de Alfviral cuando se intentaba actualizar un documento desde Share en la versión 4.2.
Al parecer, en esta versión se usa un nuevo sistema de actualización asíncrona y cuando se actualiza un documento se produce un borrado de nodo para crear otro que es el actualizado. El sistema de eventos salta con cada paso por lo que hay que verificar antes de nada que el nodo sigue todavía “vivo” y no ha sido borrado por el propio sistema de actualización. Esto, curiosamente no pasa en el contexto del Explorer (Alfresco).
En resumen, he re-factorizado algo más el código, he arreglado el problema y he reorganizado el proyecto tipo all-in-one como dos submódulos de repositorio y otro para share, así creo que está más claro y es más sencillo de instalar.
La nueva revisión se puede descargar desde: https://github.com/fegorama/alfviral/releases/tag/v1.3.2-SNAPSHOT
Este puente de 3 días de fiesta los he dedicado, entre otras cosas, a crear mis proyectos y pasarlos de Google Code (¡gracias Google!) a GitHub y a refactorizar el código del módulo para la detección de virus en Alfresco, aunque las versiones anteriores, hasta la 1.3.0-420 seguirán estando en Google Code incluido el código fuente.
El protocolo ICAP (Internet Content Adaptation Protocol) es un protocolo abierto para la redirección de contenidos con fines de filtrado y conversión. Este es muy usado para reenviar tráfico hacia antivirus, traducción, etc. En este caso, evidentemente, se utiliza para el envío de documentos de Alfresco hacia un servidor ICAP que se conecte a un antivirus, si bien, en realidad también podría utilizarse para más cosas, entre ellas traducción del documento, compresión, transformación, etc.
Se encuentra estandarizado en la RFC 3507 y para obtener más información se puede ir aquí.
He subido una nueva versión de Alfviral (Alfresco Virus Alert) adaptado para que funcione en la nueva versión 4.2 de Alfresco. Los cambios han sido en su mayoría sobre las acciones de usuario en Share.
Testeado en:
Alfresco Community 4.2c
Tomcat 7.0.47
Java JDK/Jre 1.7.0_45
Se encuentra disponible para su descarga en: http://code.google.com/p/alfviral/downloads/detail?name=fegorsoft-alfviral-1.3.0.420.zip&can=2&q=#makechanges
En muchas ocasiones es necesario el cifrado del contenido en Alfresco, en este sentido ya Alfresco en la versión 4.0 puede cifrar propiedades (http://wiki.alfresco.com/wiki/Data_Encryption) y también hay un módulo para utilizar el cifrado (http://addons.alfresco.com/addons/alfresco-encryption-module).
En este caso vamos a realizar dos acciones que cifren y descifren el contenido de un documento (propiedad content) usando el algoritmo de cifrado simétrico AES. Estos sirven para la versión 3.4 de Alfresco y siguientes.
El código también se encuentra en: http://code.google.com/p/alfcrypto
Algunas cosas importantes primero: Este software es una versión alpha o beta o como queráis llamarla pero sobre todo es un código hecho de forma más o menos rápida y por tanto no hay garantía ninguna de funcionamiento, se ha probado solo con algunos documentos MS-Word y PDF. Además, ya he detectado un problema, cuando se descifra el tipo MIME en el que se guarda la copia desencriptada es plain/text (no es que no funcione, si descargais el documento se puede abrir/editar, etc) por lo que hay que incluir en el modelo de datos una propiedad que guarde el valor original y lo restaure posteriormente (ya lo haré cuando consiga algo de tiempo). Se ha realizado solamente con carácter educativo y por lo tanto no lo recomiendo para su uso en sistemas de producción sin realizar antes algunas modificaciones y pruebas.
Otro apunte más, como bien comenta Toni de la Fuente (blyx.com), existen algunas restricciones derivadas del cifrado, la primera es en la previsualización, evidentemente no existe cuando los documentos están cifrados y no tiene sentido descifrar para previsualizar ya que rompería la seguridad. Tampoco es posible la indexación full-text ya que no es posible al igual que en contenidos de imágenes (JPG, GIF, etc.), es más, es una buena idea cambiar el tipo MIME a algún tipo que Alfresco no indexe y cuando sea descifrado reponer el tipo MIME original. El uso de este sistema sería para documentos que son necesariamente obligados a ser cifrados y solo se tenga acceso a ellos por personal especial (Recursos Humanos, I+D+I, datos con carácter especial de protección de datos, etc.) que una vez descifrados (en otra ubicación a la original principalmente) sean descargados y borrados (la copia descifrada) vaciando la papelera e incluso modificando la configuración para que no sean guardados en esta. Además recomendaría que o bien el cifrado, o el descifrado se aloje en otras unidades de disco diferentes usando para ello Content Store Selector (en este último caso solo para las versiones Enterprise).
Con todo esto, comencemos:
Lo primero que hay que hacer es construir la clase de cifrado que en este caso se llamará crypto.java:
/*
* alfcrypto is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* alfcrypto is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see .
*/
package com.fegor.alfresco.security.crypto;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;
import org.apache.log4j.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
public class Crypto {
private final Logger logger = Logger.getLogger(Crypto.class);
String password = null;
public final static int SALT_LEN = 8;
byte[] vector_init = null;
byte[] salt_pos = null;
byte[] input;
byte[] output;
Cipher eCipher = null;
Cipher deCipher = null;
private final int KEYLEN_BITS = 128;
private final int ITERATIONS = 65536;
/**
* Constructor
*/
public Crypto() {
}
/**
* Encryption configuration
*
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws NoSuchPaddingException
* @throws InvalidParameterSpecException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
* @throws UnsupportedEncodingException
* @throws InvalidKeyException
*/
public void configEncrypt() throws NoSuchAlgorithmException,
InvalidKeySpecException, NoSuchPaddingException,
InvalidParameterSpecException, IllegalBlockSizeException,
BadPaddingException, UnsupportedEncodingException,
InvalidKeyException {
SecretKeyFactory factory = null;
SecretKey tmp = null;
salt_pos = new byte[SALT_LEN];
SecureRandom rnd = new SecureRandom();
rnd.nextBytes(salt_pos);
if (logger.isDebugEnabled())
logger.debug(this.getClass().getName() + “: [salt: “
+ (new String(Hex.encodeHex(salt_pos))) + “]”);
factory = SecretKeyFactory.getInstance(“PBKDF2WithHmacSHA1”);
/*
* http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files
* .shtml
*/
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt_pos,
ITERATIONS, KEYLEN_BITS);
tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), “AES”);
eCipher = Cipher.getInstance(“AES/CBC/PKCS5Padding”);
eCipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = eCipher.getParameters();
vector_init = params.getParameterSpec(IvParameterSpec.class).getIV();
if (logger.isDebugEnabled())
logger.debug(this.getClass().getName() + “: [vector ini: “
+ (new String(Hex.encodeHex(vector_init))) + “]”);
}
/**
* Decryption configuration
*
* @param initvec
* @param salt
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws NoSuchPaddingException
* @throws InvalidKeyException
* @throws InvalidAlgorithmParameterException
* @throws DecoderException
*/
public void configDecrypt(String initvec, String salt)
throws NoSuchAlgorithmException, InvalidKeySpecException,
NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, DecoderException {
SecretKeyFactory factory = null;
SecretKey tmp = null;
SecretKey secret = null;
salt_pos = Hex.decodeHex(salt.toCharArray());
if (logger.isDebugEnabled())
logger.debug(this.getClass().getName() + “: [salt: “
+ (new String(Hex.encodeHex(salt_pos))) + “]”);
vector_init = Hex.decodeHex(initvec.toCharArray());
if (logger.isDebugEnabled())
logger.debug(this.getClass().getName() + “: [vector ini: “
+ (new String(Hex.encodeHex(vector_init))) + “]”);
/*
* http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files
* .shtml
*/
factory = SecretKeyFactory.getInstance(“PBKDF2WithHmacSHA1”);
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt_pos,
ITERATIONS, KEYLEN_BITS);
tmp = factory.generateSecret(spec);
secret = new SecretKeySpec(tmp.getEncoded(), “AES”);
deCipher = Cipher.getInstance(“AES/CBC/PKCS5Padding”);
deCipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(
vector_init));
}
/**
* Cipher input
*
* @param input
* – the cleartext file to be encrypted
* @param output
* – the encrypted data file
* @throws IOException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
* @throws ShortBufferException
*/
public void Cipher() throws IOException, IllegalBlockSizeException,
BadPaddingException, ShortBufferException {
try {
this.output = eCipher.doFinal(this.input);
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
}
/**
* Decipher input
*
* @param input
* – the cleartext file to be encrypted
* @param output
* – the encrypted data file
* @throws IOException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
* @throws ShortBufferException
*/
public void Decipher() throws IOException, IllegalBlockSizeException,
BadPaddingException, ShortBufferException {
try {
this.output = deCipher.doFinal(this.input);
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
}
/*
* Methods setter and getter
*/
public void setInput(byte[] input) {
this.input = input;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return (new String(Hex.encodeHex(salt_pos)));
}
public String getVectorInit() {
return (new String(Hex.encodeHex(vector_init)));
}
public byte[] getOutput() {
return this.output;
}
}
Como se observa es una clase normal con los métodos para configurar el cifrado y descifrado y la llamada para realizar las acciones correspondientes.
Utilizaremos dos aspectos para saber que documentos están cifrados y cuales han sido descifrados, el modelo de datos a utilizar será el siguiente:
Alfresco Crypto Model
Fernando González Ruano (twitter://fegorama)
1.0
<import uri="http://www.alfresco.org/model/dictionary/1.0"
prefix=”d” />
<namespace uri="http://www.fegorsoft.com/model/alfcrypto/1.0"
prefix=”acr” />
Ciphered
d:text
false
false
false
false
d:text
false
false
false
false
Deciphered
Para llamar a esta clase se necesitan dos acciones de Alfresco, estas serán CipherContent.java y DecipherContent.java:
Fichero: CipherContent.java
/*
* alfcrypto is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* alfcrypto is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see .
*/
package com.fegor.alfresco.action;
import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.util.HashMap;
import java.util.List;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import com.fegor.alfresco.model.AlfCryptoModel;
import com.fegor.alfresco.security.crypto.Crypto;
import com.google.gdata.util.common.util.Base64;
/**
* CryptoRepo Action
*
* @author fegor
*
*/
public class CipherContent extends ActionExecuterAbstractBase {
private final Logger logger = Logger.getLogger(CipherContent.class);
/*
* Services
*/
private ContentService contentService;
private NodeService nodeService;
private String password;
//
// TODO Poder usar más algoritmos que AES
//
// private String algorithm;
private String salt;
private String vector_init;
/*
* (non-Javadoc)
*
* @see
* org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl
* (org.alfresco.service.cmr.action.Action,
* org.alfresco.service.cmr.repository.NodeRef)
*/
@Override
protected void executeImpl(Action action, NodeRef actionedUponNodeRef) {
if (!nodeService.hasAspect(actionedUponNodeRef,
AlfCryptoModel.ASPECT_CIPHERED)) {
if (logger.isDebugEnabled()) {
logger.debug(this.getClass().getName() + “: [Action for: “
+ actionedUponNodeRef + ” is ciphering…]”);
}
if (actionedUponNodeRef != null)
try {
this.cryptoFileCipher(actionedUponNodeRef);
} catch (ContentIOException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
* (non-Javadoc)
*
* @see org.alfresco.repo.action.ParameterizedItemAbstractBase#
* addParameterDefinitions(java.util.List)
*/
@Override
protected void addParameterDefinitions(List arg0) {
}
/**
* Crypto file for nodeRef
*
* @param nodeRef
* @throws IOException
* @throws ContentIOException
*/
private void cryptoFileCipher(NodeRef nodeRef) throws ContentIOException,
IOException {
ContentReader contentReader = this.contentService.getReader(nodeRef,
ContentModel.PROP_CONTENT);
ContentWriter contentWriter = this.contentService.getWriter(nodeRef,
ContentModel.PROP_CONTENT, true);
if (contentReader != null) {
Crypto crypto = new Crypto();
crypto.setPassword(this.password);
byte[] crb = IOUtils.toByteArray(contentReader
.getContentInputStream());
try {
crypto.configEncrypt();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidParameterSpecException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
crypto.setInput(crb);
try {
crypto.Cipher();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (ShortBufferException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
contentWriter.putContent(Base64.encode(crypto.getOutput()));
this.salt = crypto.getSalt();
this.vector_init = crypto.getVectorInit();
this.removeAspect(nodeRef);
this.addAspect(nodeRef);
} else {
if (logger.isDebugEnabled())
logger.debug(this.getClass().getName()
+ “: [contentReader is null]”);
}
}
/**
* Remove aspect Deciphered
*
* @param nodeRef
*/
private void removeAspect(NodeRef nodeRef) {
if (nodeService.hasAspect(nodeRef, AlfCryptoModel.ASPECT_DECIPHERED)) {
nodeService.removeAspect(nodeRef, AlfCryptoModel.ASPECT_DECIPHERED);
}
}
/**
* Add aspect Ciphered
*
* @param nodeRef
*/
private void addAspect(NodeRef nodeRef) {
HashMap properties = new HashMap(
1, 1.0f);
properties.put(AlfCryptoModel.PROP_SALT, this.salt);
properties.put(AlfCryptoModel.PROP_VECTOR_INIT, this.vector_init);
if (!nodeService.hasAspect(nodeRef, AlfCryptoModel.ASPECT_CIPHERED)) {
nodeService.addAspect(nodeRef, AlfCryptoModel.ASPECT_CIPHERED,
properties);
}
}
/**
* @param contentService
*/
public void setContentService(ContentService contentService) {
this.contentService = contentService;
}
/**
* @param nodeService
*/
public void setNodeService(NodeService nodeService) {
this.nodeService = nodeService;
}
/**
* @param password
*/
public void setPassword(String password) {
this.password = password;
}
/**
* @param algorithm
*/
//
// TODO Poder usar más algoritmos que AES
//
// public void setAlgorithm(String algorithm) {
// this.algorithm = algorithm;
// }
}
Fichero: DecipherContent.java
/*
* alfcrypto is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* alfcrypto is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see .
*/
package com.fegor.alfresco.action;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.List;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.apache.commons.codec.DecoderException;
import org.apache.log4j.Logger;
import com.fegor.alfresco.model.AlfCryptoModel;
import com.fegor.alfresco.security.crypto.Crypto;
import com.google.gdata.util.common.util.Base64;
import com.google.gdata.util.common.util.Base64DecoderException;
/**
* DecryptoRepo Action
*
* @author fegor
*
*/
public class DecipherContent extends ActionExecuterAbstractBase {
private final Logger logger = Logger.getLogger(DecipherContent.class);
/*
* Services
*/
private ContentService contentService;
private NodeService nodeService;
private String password;
//
// TODO Poder usar más algoritmos que AES
//
// private String algorithm;
private String salt;
private String vector_init;
/*
* (non-Javadoc)
*
* @see
* org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl
* (org.alfresco.service.cmr.action.Action,
* org.alfresco.service.cmr.repository.NodeRef)
*/
@Override
protected void executeImpl(Action action, NodeRef actionedUponNodeRef) {
if (nodeService.hasAspect(actionedUponNodeRef,
AlfCryptoModel.ASPECT_CIPHERED)) {
if (logger.isDebugEnabled()) {
logger.debug(this.getClass().getName() + “: [Action for: “
+ actionedUponNodeRef + ” is deciphering…]”);
}
if (actionedUponNodeRef != null)
try {
this.cryptoFileDecipher(actionedUponNodeRef);
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (DecoderException e) {
e.printStackTrace();
}
}
}
/*
* (non-Javadoc)
*
* @see org.alfresco.repo.action.ParameterizedItemAbstractBase#
* addParameterDefinitions(java.util.List)
*/
@Override
protected void addParameterDefinitions(List arg0) {
}
/**
* Scan file for nodeRef
*
* @param nodeRef
* @throws DecoderException
* @throws InvalidAlgorithmParameterException
*/
private void cryptoFileDecipher(NodeRef nodeRef)
throws InvalidAlgorithmParameterException, DecoderException {
ContentReader contentReader = this.contentService.getReader(nodeRef,
ContentModel.PROP_CONTENT);
ContentWriter contentWriter = this.contentService.getWriter(nodeRef,
ContentModel.PROP_CONTENT, true);
if (contentReader != null) {
byte[] crb = contentReader.getContentString().getBytes();
Crypto crypto = new Crypto();
crypto.setPassword(this.password);
this.salt = (String) nodeService.getProperty(nodeRef,
AlfCryptoModel.PROP_SALT);
this.vector_init = (String) nodeService.getProperty(nodeRef,
AlfCryptoModel.PROP_VECTOR_INIT);
try {
crypto.configDecrypt(this.vector_init, this.salt);
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}
try {
crypto.setInput(Base64.decode(crb));
} catch (Base64DecoderException e1) {
e1.printStackTrace();
}
try {
crypto.Decipher();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (ShortBufferException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
contentWriter.setMimetype(“text/plain”);
contentWriter.putContent((InputStream) (new ByteArrayInputStream(
crypto.getOutput())));
this.removeAspect(nodeRef);
this.addAspect(nodeRef);
} else {
if (logger.isDebugEnabled())
logger.debug(this.getClass().getName()
+ “: [contentReader is null]”);
}
}
/**
* Remove aspect Ciphered
*
* @param nodeRef
*/
private void removeAspect(NodeRef nodeRef) {
if (nodeService.hasAspect(nodeRef, AlfCryptoModel.ASPECT_CIPHERED)) {
nodeService.removeAspect(nodeRef, AlfCryptoModel.ASPECT_CIPHERED);
}
}
/**
* Add aspect Deciphered
*
* @param nodeRef
*/
private void addAspect(NodeRef nodeRef) {
if (!nodeService.hasAspect(nodeRef, AlfCryptoModel.ASPECT_CIPHERED)) {
nodeService
.addAspect(nodeRef, AlfCryptoModel.ASPECT_CIPHERED, null);
}
}
/**
* @param contentService
*/
public void setContentService(ContentService contentService) {
this.contentService = contentService;
}
/**
* @param nodeService
*/
public void setNodeService(NodeService nodeService) {
this.nodeService = nodeService;
}
/**
* @param password
*/
public void setPassword(String password) {
this.password = password;
}
/**
* @param algorithm
*/
//
// TODO Poder usar más algoritmos que AES
//
// public void setAlgorithm(String algorithm) {
// this.algorithm = algorithm;
// }
}
El siguiente paso es configurar y registrar las acciones en Alfresco:
Fichero: actions-context.xml
true
classpath:alfresco/extension/alfcrypto.properties
<bean id="alfcrypto.cipher.action" class="com.fegor.alfresco.action.CipherContent"
parent=”action-executer”>
<!–
${alfviral.algorithm}
–>
${alfviral.password}
<bean id="alfcrypto.decipher.action" class="com.fegor.alfresco.action.DecipherContent"
parent=”action-executer”>
<!–
${alfviral.algorithm}
–>
${alfviral.password}
Fichero: model-context.xml
alfresco/module/alfcrypto/model/alfcryptoModel.xml
Fichero: webclient-context.xml
alfresco.module.alfcrypto.messages.alfcrypto
<bean id="alfcrypto.webclient.configBootstrap" class="org.alfresco.web.config.WebClientConfigBootstrap"
init-method=”init”>
classpath:alfresco/module/alfcrypto/ui/web-client-config-custom.xml
Fichero: web-client-config-custom.xml
Fichero: alfcrypto.properties (messages)
alfcrypto.cipher.action.title=Cifrar
alfcrypto.cipher.action.description=Cifrado del contenido
alfcrypto.decipher.action.title=Descifrar
alfcrypto.decipher.action.description=Descifrado del contenido
alfcrypto.label.ciphered=Cifrado
alfcrypto.label.deciphered=Descifrado
Y por último construir el fichero de configuración:
Fichero: alfcrypto.properties
# La elección de algoritmo (alfcrypto.algorithm) no está implementado todavía
alfcrypto.algorithm=AES
alfcrypto.password=estoesunaclavesecreta
A partir de aquí podemos utilizar el sistema para cifrar y descifrar, para ello podemos usar reglas para que el contenido en una carpeta sea cifrado, crear un workflow para descifrar el contenido de los documentos cifrados, etc.
Solo cifra el contenido, no las propiedades de los documentos.
Más sobre este tema:
http://es.wikipedia.org/wiki/Advanced_Encryption_Standard
http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
http://wiki.alfresco.com/wiki/Data_Encryption
http://addons.alfresco.com/addons/alfresco-encryption-module
Debido a un “desliz” en mi código con la versión 1.2.0 se me olvidó declarar el servicio NodeService para usarlo en los modos COMMAND y INSTREAM por lo que he arreglado esto y algún que otro retoque más y he subido la versión 1.2.1 que os recomiendo que instaléis en lugar de la versión anterior.
Agradezco a Niccolò Pasquetto su aportación y aviso.
La nueva versión se puede descargar directamente desde:
https://alfviral.googlecode.com/files/fegorsoft-alfviral-1.2.1.zip
Hace unos días recibí una solicitud de un usuario sobre mi módulo para escanear ficheros desde Alfresco. La nueva funcionalidad solicitada era la de poder seleccionar qué ficheros queremos que sean enviados al antivirus que tengamos configurado.
Anoche estuve retocando un poco el módulo e incluí la funcionalidad en forma de ficheros excluidos, es decir, una lista de ficheros que no queremos que se envíen al antivirus para ser escaneados, en este caso podemos incluir ficheros PDF, TXT, CSV, etc. y no se enviarán para ser analizados. En la nueva configuración solo hay un parámetro más configurado por ejemplo como sigue:
alfviral.file.exceptions=text/html|text/xml|application/pdf|image/jpeg|text/plain
Donde en la lista y separada por “|” podemos incluir los archivos según su “Content-Type”.
Espero que con esta nueva funcionalidad pueda “discriminarse” mejor que documentos/ficheros necesitamos que sean analizados.
He incluido el módulo como versión 1.2.0.
Por fin el 21 de agosto liberé la versión 1.1.0 del módulo para escaneo de documentos mediante antivirus llamado Alfviral (el nombre a lo mejor no es muy apropiado pero fue el que se me ocurrió en ese momento).
Básicamente lo que se ha incluido ha sido una pequeña refactorización del código y la inclusión de la posibilidad de poder escanear los documentos mediante la página www.virustotal.com que a su vez utiliza más de 40 motores de antivirus.
Las características actuales del módulo son:
1. Escanear en base a tres modos distintos:
COMMAND: Permite utilizar un script o directamente el programa ejecutable del antivirus que se quiera siempre y cuando permita al menos un parámetro que sea el fichero a escanear. Este proceso depende del arranque del comando antivirus en si.
INSTREAM: Utilizado para lanzar un flujo de datos del fichero en Alfresco hacia el puerto (3310TCP) utilizado por ClamAV. Esto permite tener un antivirus ClamAV central en un servidor y utilizarlo remotamente desde Alfresco.
VIRUSTOTAL: En este modo se sube el fichero que sube o se actualiza en Alfresco a la web virustotal.com vía HTTP mediante el método POST y se recoge el resultado mediante JSON para su análisis.
2. Si el documento se detecta como infectado se añade el aspecto “infected” así como un aspecto adicional dependiendo del método utilizado para su análisis.
3. Uso de las “policies” de Alfresco usando los métodos onContentUpdate y onContentRead para analizar los documentos que son subidos, modificados y/o leídos.
4. Se puede utilizar el análisis mediante una programación de tiempo indicando a partir de qué espacio se quiere analizar y si se utiliza en profundidad, es decir, a los subespacios.
5. Implemena acciones de usuario tanto en la interface /alfresco como en /share para poder analizar documentos de forma interactiva.
6. Uso de reglas para personalizar los análisis utilizando la acción de escanear.
7. Facilidad de instalación utilizando las Alfresco Module Management Tools para la parte de repositorio y copiando directamente una librería en extension/lib para Share.
8. Configuración flexible y sencilla para establecer el modo de análisis y las formas en las que se quiere realizar el análisis:
– Al subir/modificar un documento
– Al leer un documento
– En una programación horaria y/o de fecha concreta
– Desde una carpeta (espacio de trabajo) en adelante
Un ejemplo de configuración es:
# Command to exec, i.e. clamscan, alfviral.sh, etc. alfviral.command=C:\Users\fegor\Documents\alfviral.bat # Config for ClamAV in stream data alfviral.timeout=30000 alfviral.host=127.0.0.1 alfviral.port=3310 #Config for VIRUSTOTAL vt.key=246df658bca5e096895683c01ba4bd2eb3a00303b506bda774b71488134bf984 vt.url=https://www.virustotal.com/vtapi/v2/file/scan # Modes: COMMAND, INSTREAM, VIRUSTOTAL alfviral.mode=VIRUSTOTAL # Events alfviral.on_update=TRUE alfviral.on_read=FALSE # Scheduled action alfviral.scheduled.pathQuery=/app:company_home/st:sites alfviral.scheduled.cronExpression=* * 3 * * ? 2099
Toda la información tanto en español como en inglés está en la página principal del proyecto: http://code.google.com/p/alfviral/
Como es un proyecto libre y personal las mejoras y nuevas funcionalidades dependen mucho de mi tiempo libre pero más o menos algunas ideas que tengo en mente son:
– Añadir estadísticas sobre los documentos infectados
– Posibilidad de mover los documentos infectados a un espacio de cuarentena
– Recuperación de los documentos infectados a su ubicación original si son desinfectados o se eliminan los aspectos de infección.
– Añadir nuevos protocolos de comunicación con antivirus (Symantec, McAfee,…)
– Poder utilizar distintos modos de análisis al mismo tiempo y para diferentes objetos (espacios y ficheros)
—
Swithun Crowe ha realizado una “custom action” para poder escanear un documento a petición. Es otra idea más y esta es totalmente “bajo demanda” ya que es mediante una acción. Como es otra posibilidad se podría adaptar perfectamente a “Alfresco Virus Alert” y tenerla como otra posibilidad más dentro del marco de seguridad y protección de antivirus para Alfresco.
La entrada en la wiki es: http://wiki.alfresco.com/wiki/Antivirus
Interface
|
Method
|
org.alfresco.repo.content.ContentServicePolicies
|
onContentUpdate
|
onContentRead
|
Primero definimos los beans que usamos en el “behavior”: alfviral-behavior-context.xml
true
property>
classpath:alfresco/extension/alfviral-behavior.properties
<bean id="AlfViralBehavior" class="com.fegor.alfresco.behavior.OnUpdateReadScan"
init-method=”init”>
${alfviral.command}
${dir.contentstore}
bean>
Seguidamente del fichero de propiedades: alfviral-behavior.properties
alfviral.command=/usr/bin/clamscan
Montamos el modelo de datos: alfviral-model-context.xml
alfresco/extension/alfviralModel.xml
El modelo: alfviralModel.xml
Alfresco Virus Alarm Model
Fernando González Ruano (twitter://fegorara)
1.0
Infected
d:date
false
d:boolean
false
La parte para el cliente web del browser: web-client-config-custom.xml
El fichero de propiedades para las etiquetas: webclient.properties
ava_date=Fecha de detecciu00F3n
ava_clean=u00BFDesinfectado?
Y por último la clase java: OnUpdateReadScan.java
package com.fegor.alfresco.behavior;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.repo.content.ContentServicePolicies;
import org.alfresco.repo.policy.Behaviour;
import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.apache.log4j.Logger;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.repository.ContentReader;
/**
* Integrates antivirus scanning documents for alfresco
*
* Implements the policies of “OnContentUpdate” and “OnContentRead”.
*
* @author Fernando González Ruano (fegor)
*/
public class OnUpdateReadScan
implements ContentServicePolicies.OnContentUpdatePolicy,
ContentServicePolicies.OnContentReadPolicy
{
private Logger logger = Logger.getLogger(OnUpdateReadScan.class);
// behaviours
private Behaviour onContentUpdate;
private Behaviour onContentRead;
// dependencias
private PolicyComponent policyComponent;
private ContentService contentService;
private NodeService nodeService;
// configuration
private List command;
private String store;
private final String NAMESPACE_ALFVIRAL_CONTENT_MODEL = “alfviral.model”;
private final QName ASPECT_INFECTED = QName.createQName(NAMESPACE_ALFVIRAL_CONTENT_MODEL, “infected”);
private final QName PROP_INFECTED_DATE = QName.createQName(NAMESPACE_ALFVIRAL_CONTENT_MODEL, “date”);
private final QName PROP_INFECTED_CLEAN = QName.createQName(NAMESPACE_ALFVIRAL_CONTENT_MODEL, “clean”);
Map aspectValues = new HashMap();
// método de inicio
public void init ()
{
if (logger.isDebugEnabled()) logger.debug(“Start OnUpdateReadScan.”);
// crear behaviours
this.onContentUpdate = new JavaBehaviour(this,
“onContentUpdate”,
NotificationFrequency.TRANSACTION_COMMIT);
this.onContentRead = new JavaBehaviour(this,
“onContentRead”,
NotificationFrequency.TRANSACTION_COMMIT);
// binding “policies”
this.policyComponent.bindClassBehaviour(QName.createQName
(NamespaceService.ALFRESCO_URI,
“onContentUpdate”),
“cm:content”, this.onContentUpdate);
this.policyComponent.bindClassBehaviour(QName.createQName
(NamespaceService.ALFRESCO_URI,
“onContentRead”),
“cm:content”, this.onContentRead);
}
@Override
public void onContentUpdate (NodeRef nodeRef, boolean flag)
{
ContentReader contentReader = this.contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
// full path of file
String contentUrl = contentReader.getContentUrl();
String contentPath = contentUrl.replaceFirst(“store:/”, this.store);
if (logger.isDebugEnabled())
{
logger.debug(“(Update) “+this.command+” “+contentPath);
}
else if (logger.isInfoEnabled())
{
logger.info(“(Update) Llamando al script de escaneo de virus para “+nodeRef.getId());
}
try
{
// execute command “antivir contentPath”
this.command.add(contentPath);
ProcessBuilder pb = new ProcessBuilder(this.command);
Process process = pb.start();
int intResult = process.waitFor();
logger.debug(“(Update) Resultado del escaneo: “+intResult);
// if result is not 0, file is infected
if (intResult != 0)
{
logger.info(“ALERTA ** El fichero: “+contentReader.getContentUrl()+” está infectado. **”);
// add aspect Infected is not assigned
if (!nodeService.hasAspect(nodeRef, this.ASPECT_INFECTED))
{
this.aspectValues.put(this.PROP_INFECTED_DATE, new Date());
this.aspectValues.put(this.PROP_INFECTED_CLEAN, false);
nodeService.addAspect(nodeRef, this.ASPECT_INFECTED, this.aspectValues);
// TODO Other actions… quarantine, delete, send email, etc.
}
else
{
logger.debug(“Este fichero se detectó como infectado anteriormente.”);
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
@Override
public void onContentRead (NodeRef nodeRef)
{
// TODO Actions for read content
}
// Setters…
public void setPolicyComponent(PolicyComponent policyComponent)
{
this.policyComponent = policyComponent;
}
public void setContentService(ContentService contentService)
{
this.contentService = contentService;
}
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
public void setStore(String store)
{
this.store = store;
}
public void setCommand(List command)
{
this.command = command;
}
}
A partir de aquí…
Esto es solo un ejemplo de la multitud de acciones y configuraciones que pueden realizarse en este sentido, por ejemplo, falta el código para el evento OnContentRead, falta alguna acción de mover los documentos infectados a algún espacio de cuarentena, avisar al administrador y al usuario de la detección, internacionalizar, etc. pero creo que es un buen punto de partida para que cada cual adapte esta solución a su manera. No os lo voy a dar todo hecho ¿verdad?… 😉
Finalmente dos capturas de pantalla:
Un extracto del log (poniendo en el log4j.properties el valor log4j.logger.com.fegor=debug):
10:44:16,141 User:admin DEBUG [alfresco.behavior.OnUpdateReadScan] (Update) [/usr/local/bin/clamscan, /home/alfresco/enterprise/alf_data/contentstore/2010/12/30/8/3/30a7961f-cdef-4410-9ba0-ca94a8542d03.bin, /home/alfresco/enterprise/alf_data/contentstore/2010/12/30/10/42/4a6a4884-c14e-4b24-ae22-c1e2a9839593.bin, /home/alfresco/enterprise/alf_data/contentstore/2010/12/30/10/42/4a6a4884-c14e-4b24-ae22-c1e2a9839593.bin] /home/alfresco/enterprise/alf_data/contentstore/2010/12/30/10/44/b9b5c69c-cff5-400e-9f63-4888ca745749.bin
10:44:20,125 User:admin DEBUG [alfresco.behavior.OnUpdateReadScan] (Update) Resultado del escaneo: 1
10:44:20,126 User:admin INFO [alfresco.behavior.OnUpdateReadScan] ALERTA ** El fichero: store://2010/12/30/10/44/b9b5c69c-cff5-400e-9f63-4888ca745749.bin está infectado. **
Y algo de bibliografía que podéis consultar:
Libro: Alfresco Developer Guide
Autor: Jeff Potts
ISBN: 978-1-847193-11-7
Web de Jeff Potts: http://ecmarchitect.com/
Wiki de Alfresco: http://wiki.alfresco.com/wiki/Policy_Component
Web de ClamAV: http://www.clamav.net/lang/en/