martedì 28 agosto 2012

Offuscare codice Java con PROGUARD

Utilizzando PROGUARD è possibile effettuare un efficace offuscamento del codice e anche comprimere nel caso sia necessario ottimizzare spazio.
Si utilizza da riga di comando.
Per usarlo ho per prima cosa scritto un progetto di prova, un semplice java project che effettua una criptazione utilizzando l'algoritmo AES con chiave a 16 bit.

Testato il programma ho creato il jar.
Quindi ho esportato il jar dentro la cartella lib di PROGUARD.
Ho inserito sempre qui sotto la lib di PROGUARD anche il jar commons-codec-1.3.jar che è utilizzato dal mio codice.
Quindi ho creato (sempre sotto la lib) un file denominato parametri.pro così definito

-injars  cipher.jar
-outjars cipher_obs.jar

-libraryjars C:\Programmi\Java\jdk1.6.0_29\jre\lib\rt.jar;C:\Programmi\Java\jdk1.6.0_29\jre\lib\jce.jar;commons-codec-1.3.jar

-keep public class it.oasi.cipher.Cypher {
    public static void main(java.lang.String[]);
}




injars e outjars rappresentano rispettivamente il jar da offuscare e il jar prodotto dall'offuscamento

libraryjars indica tutte le librerie (comprese quelle generali della jdk come si può vedere) utilizzate dal jar. Si noti che ho onserito anche il jce.jar per avere il supporto della classe javax.crypto.Cipher

keep serve a mantenere l'entry point del codice (in questo caso il codice main)

Per generare il jar offuscato posizionarsi via shell sulla directory lib e digitare il seguente comando

 java -jar proguard.jar @parametri.pro


Il risultato è il seguente

Classe originale

import java.io.IOException;
import java.security.Key;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

public class Cypher
{
  private static Cypher istanza;


public static Cypher getInstance(){
      if(istanza==null){
          istanza=new Cypher();
      }
      return istanza;
  }

  private  Key getKey()
  {
    Date data = getDateForKey();
    SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
    String chiave = sdf.format(data) + "abcdre";
   
    byte[] rawdata = chiave.getBytes();
   
    SecretKeySpec skeySpec = new SecretKeySpec(rawdata, "AES");
    return skeySpec; }

  private  Date getDateForKey() {
    Calendar c = Calendar.getInstance();
    c.set(1, 1978);
    c.set(2, 3);
    c.set(5, 20);
    return c.getTime(); }

  private  byte[] encrypt(String valore) throws Exception {
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(1, getKey());
    return cipher.doFinal(valore.getBytes());
  }

  private  byte[] decrypt(byte[] valore) throws Exception {
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(2, getKey());
    return cipher.doFinal(valore);
  }

  public  String cifra(String valore) throws Exception {
    return toBase64(encrypt(valore));
  }

  public  String decifra(String valore) throws Exception {
        return new String(decrypt(fromBase64(valore)));
      }
  private  String toBase64(byte[] valore)
  {
    return new String(new Base64().encode(valore));
  }

  private  byte[] fromBase64(String valore)
    throws IOException
  {
    return new Base64().decode(valore.getBytes());
  }

  public static void main(String[] args)
    throws Exception
  {
      Cypher c=Cypher.getInstance();
     
   String cifratoB64 = c.cifra("abilitatoDrillDown");
   String decifrato = c.decifra(cifratoB64);
    System.out.println("Cifrato: " + cifratoB64);
    System.out.println("Decifrato: " + decifrato);
  }
}


Se provo a decompilare il jar offuscato ottengo qualcosa di vagamente incomprensibile:


import java.io.PrintStream;
import java.security.Key;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;

public class Cypher
{
  private static Cypher a;

  private Key a()
  {
    (localObject = Calendar.getInstance()).set(1, 1978);
    ((Calendar)localObject).set(2, 3);
    ((Calendar)localObject).set(5, 20);
    Object localObject = ((Calendar)localObject).getTime();
    SimpleDateFormat localSimpleDateFormat = new SimpleDateFormat("dd-MM-yyyy");
    localObject = (localObject = localSimpleDateFormat.format((Date)localObject) + "abcdre").getBytes();
    return (Key)(localObject = new SecretKeySpec(localObject, "AES"));
  }

  public static void main(String[] paramArrayOfString)
  {
    if (a == null)
      a = new Cypher();
    Object localObject2 = "abilitatoDrillDown";
    localObject2 = localObject2;
    Object localObject1 = localObject1 = paramArrayOfString = a;
    Cipher localCipher;
    (localCipher = Cipher.getInstance("AES")).init(1, ((Cypher)localObject1).a());
    localObject1 = localCipher.doFinal(((String)localObject2).getBytes());
    String str = new String(new Base64().encode(localObject1));
    localObject2 = str;
    localObject1 = paramArrayOfString;
    localObject1 = localObject2;
    localObject2 = new Base64().decode(((String)localObject1).getBytes());
    localObject1 = localObject1;
    (localCipher = Cipher.getInstance("AES")).init(2, ((Cypher)localObject1).a());
    paramArrayOfString = new String(localCipher.doFinal(localObject2));
    System.out.println("Cifrato: " + str);
    System.out.println("Decifrato: " + paramArrayOfString);
  }
}


NB: in questo caso anche importando il jar su un  nostro progetto sarà visibile solo il metodo main, per fare in modo che lo siano anche altri metodi occorre specificarli qui.

Esempio:

-keep public class it.oasi.cipher.Cypher {
    public static void main(java.lang.String[]);
    public static it.oasi.cipher.Cypher getInstance();
    public java.lang.String decifra(java.lang.String );
}


Da notare come le variabili di input non vanno specificati così come i tipi, se disgraziatamente doveste farlo (ad esempio nel decifra dopo il tipo di input definire la variabile valore) si incappa nel seguente messaggio di errore

Error: Expecting separating ',' or closing ')' before 'valore' in line 8 of file
 'parametri.pro',
  included from argument number 1

lunedì 20 agosto 2012

Sql Server select cast

Con Sql server per ottenere valori decimali si può utilizzare la sintassi

select cast([numero] as decimal(5,2))


Se facciamo così però:

select cast(3/5 as decimal(5,2))


Otteniamo 0, un modo veloce per ottenere il risultato corretto è quello di anteporre 1. * all'espressione, in questo caso il risultato è 0.6 correttamente

Facendo

select cast(1.*3/5 as decimal(5,2))



martedì 14 agosto 2012

Web Service con WS-Security seconda parte (CLIENT)

Per generare un client di un Web Service messo in sicurezza come visto nel precedente post occorre per prima cosa tramite l'utility wsimport (vedi qui ) generare le classi del client e dopo aggiungere l'handler che si occuperà in questo caso di scrivere username e password nell'header soap.

Ho scritto 2 classi una è l'implementazione dell'HandlerResolver usati in JAX-WS 2.0 proprio per "prendere il controllo" della eventuale catena di Handler presenti (in questo caso sarà soltanto uno).

Poi invece bisogna scrivere l'handler specifico che implementerà al solito l'interfaccia SOAPHandler.


MyHandler

import java.util.ArrayList;
import java.util.List;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.HandlerResolver;
import javax.xml.ws.handler.PortInfo;
import javax.xml.ws.handler.soap.SOAPHandler;
public class MyHandler implements HandlerResolver {

    @Override
    public List<Handler> getHandlerChain(PortInfo portInfo) {
        List<Handler> handlerList = new ArrayList<Handler>();
        SOAPHandler handler =  new WSSecurityHandler();
        handlerList.add(handler);
        return handlerList;
    }

}

WsSecurityHandler

package it.handler;

import java.io.IOException;
import java.util.Set;
import java.util.TreeSet;

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFactory;
import javax.xml.soap.SOAPHeader;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
/**
 * Handler che gestisce il passaggio <br>
 * di username e password nell'header soap
 * @author
 *
 */
public class WSSecurityHandler implements SOAPHandler {
    private boolean scriviOutputSuConsole=true;
    /**
     * INSERIRE LO USERNAME
     */
    public static final String USER_NAME="pippo";
    /**
     * INSERIRE LA PWD 
     */
    public static final String PWD="pluto";
    @Override
    public void close(MessageContext arg0) {
        if(scriviOutputSuConsole) System.out.println("CLOSE");
    }

    @Override
    public boolean handleFault(MessageContext context1) {
        SOAPMessageContext context = (SOAPMessageContext)context1;
        try {
            if(scriviOutputSuConsole)context.getMessage().writeTo(System.out);
        } catch (SOAPException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return false;
    }

    @Override
 
    public boolean handleMessage(MessageContext context1) {
        SOAPMessageContext context = (SOAPMessageContext)context1;
        Boolean outbound = (Boolean)context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
        if (outbound.booleanValue())
        {
            try {
                SOAPEnvelope envelope = context.getMessage().getSOAPPart().getEnvelope();
                SOAPFactory factory = SOAPFactory.newInstance();
                String prefix = "wsse";
                String uri = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
                SOAPElement securityElement = factory.createElement("Security", prefix, uri);
                QName nameMust = new QName(envelope.getPrefix()+":mustUnderstand");
                securityElement.addAttribute(nameMust, "1");
                securityElement.addNamespaceDeclaration("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
                SOAPElement tokenElement = factory.createElement("UsernameToken", prefix, uri);
                QName nameWSU = new QName("wsu:Id");
                tokenElement.addAttribute(nameWSU, "token-1-1236072936329-25515818");
                SOAPElement username = factory.createElement("Username", prefix, uri);
                username.addTextNode(USER_NAME);
                SOAPElement pwd = factory.createElement("Password", prefix, uri);
                pwd.addTextNode(PWD);
                tokenElement.addChildElement(username);
                tokenElement.addChildElement(pwd);
                securityElement.addChildElement(tokenElement);
                SOAPHeader header = envelope.addHeader();
                header.addChildElement(securityElement);
                if(scriviOutputSuConsole) context.getMessage().writeTo(System.out);
               
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        else
        {
        }
        return true;
    }

    @Override
    public Set getHeaders() {
        return new TreeSet();
    }
}



A questo punto dal nostro client Java dobbiamo scrivere il seguente codice (supponiamo di aver messo in sicurezza il Web Service visto in questo post )

FattorialeDaoImplService f=new FattorialeDaoImplService();
CalcoloWs port=f.getCalcoloWsPort();
HandlerResolver resolver = new MyHandler();
f.setHandlerResolver(resolver);    
         
long fatt=port.getFattoriale(5);                 
System.out.println(fatt);



Web Service con WS-Security prima parte (SERVER)

Vediamo in questo esempio come realizzare un WS protetto da una username e una password  (in modalità token profile).
In questo post non verrà trattata l'implementazione del Ws, vista in altri post ma solo descritta la procedura di messa in sicurezza. 
Per effettuare una operazione di questo tipo si utilizzano gli Handler soap, che analizzano il contenuto presente nell'header e recuperano li le informazioni necessarie all'autenticazione.


La prima operazione da fare è quella di annotare il nostro Web Service così:

 @HandlerChain(file = "handlers.xml")



Il file handlers.xml definisce l'handler che si occupa di parsare la parte dell'header.

E' definito così:

<?xml version="1.0" encoding="UTF-8"?>
<handler-chains xmlns="http://java.sun.com/xml/ns/javaee">
  <handler-chain>
    <handler>
      <handler-name>it.security.handler.WsSecurityHandler</handler-name>
      <handler-class>it.security.handler.WsSecurityHandler</handler-class>
    </handler>
  </handler-chain>
</handler-chains>

WsSecurityHandler

package it.security.handler;
import it.dao.HeaderParserDao;
import it.daoImpl.HeaderParserDaoImpl;
import it.daoImpl.SecurityDaoImpl;
import it.exception.AuthenticationException;
import it.util.Log;
import java.io.ByteArrayOutputStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import org.apache.log4j.Logger;
public class WsSecurityHandler implements SOAPHandler<SOAPMessageContext>
{
      private static final Logger log = Log.get(WsSecurityHandler.class);

      public Set<QName> getHeaders() {
        Set headers = new HashSet();
        QName name = new QName(
          "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
          "Security");
        headers.add(name);
        return headers;
      }

      public void close(MessageContext arg0)
      {
      }

      public boolean handleFault(SOAPMessageContext arg0)
      {
        return false;
      }

      public boolean handleMessage(SOAPMessageContext smc)
      {
        
        Boolean outboundProperty =
          (Boolean)smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
        if (!outboundProperty.booleanValue())
        {
          log.debug("Messaggio in ingresso");
          verificaSicurezza(smc);
        }
        return true;
      }

      private boolean verificaSicurezza(SOAPMessageContext smc) {
        SOAPMessageContext context = smc;
        String username = "";
        boolean verifica = false;
        try {
          ByteArrayOutputStream bas = new ByteArrayOutputStream();
          Set headers = getHeaders();
          Iterator it = headers.iterator();
          while (it.hasNext()) {
            QName q = (QName)it.next();
            log.info("QNAME :" + q.getNamespaceURI());
          }
          context.getMessage().writeTo(bas);
          String envelope = new String(bas.toByteArray(), "UTF-8");
          bas.close();
          log.debug("ENVELOPE:" + envelope);

          if (!envelope.contains("wsse:Security"))
          {
            log.error("Attenzione, il servizio richiede autenticazione");
            throw new AuthenticationException("Attenzione, il servizio richiede autenticazione");
          }

          SecurityDaoImpl aut=SecurityDaoImpl.getInstance();
          HeaderParserDao parser = HeaderParserDaoImpl.getInstance(envelope);
          username = parser.getUserNameFromHeader();
          verifica = aut.verifica(username, parser.getPasswordFromHeader());
        }
        catch (Exception e)
        {
          if (!verifica) throw new AuthenticationException(e);
        }
        if (!verifica) {
          throw new AuthenticationException("Credenziali dell'utente '" + username + "' non corrette.");
        }
        return verifica;
      }

}


HeaderParserDao e HeaderParserDaoImpl sono una classe e una interfaccia che mi sono scritto per effettuare il parsing dell'xml, le riporto per comodità.
La classe SecurityDaoImpl invece si occupa dell'autenticazione (prende in input user e password) e non la riporto in quanto non importante ai fini del post seguente.

HeaderParserDao

public  interface HeaderParserDao
{
  public  String getUserNameFromHeader();

  public  String getPasswordFromHeader();
}

HeaderParserDaoImpl

package it.daoImpl;

import it.dao.HeaderParserDao;
import it.exception.HeaderParserException;

import java.io.StringReader;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.log4j.Logger;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import it.util.*;

public class HeaderParserDaoImpl implements HeaderParserDao {

     private static Logger log = Logger.getLogger("MyLogger");
    private static HeaderParserDaoImpl istanza;
      private String xml;
      private String username;
      private String password;

      private HeaderParserDaoImpl(String xml)
      {
        this.xml = xml;
      }

      public static HeaderParserDaoImpl getInstance(String xml) {
        istanza = new HeaderParserDaoImpl(xml);
        istanza.parsaXml();

        return istanza;
      }
    @Override
    public String getUserNameFromHeader() {
        return this.username;
    }

    @Override
    public String getPasswordFromHeader() {
        return this.password;
    }
   
     private void parsaXml() {
            try {
              DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
              DocumentBuilder db = dbf.newDocumentBuilder();
              InputSource is = new InputSource();
              is.setCharacterStream(new StringReader(this.xml));
              Document doc = db.parse(is);
              NodeList nodoSecurity = doc.getElementsByTagName("wsse:Security");
              if (nodoSecurity == null) throw new SecurityException("Attenzione, il servizio richiede autenticazione");
              NodeList nodes = doc.getElementsByTagName("wsse:UsernameToken");
              if (nodes == null) throw new SecurityException("Attenzione, il servizio richiede autenticazione");

              for (int i = 0; i < nodes.getLength(); i++) {
                Element element = (Element)nodes.item(i);

                NodeList name = element.getElementsByTagName("wsse:Username");
                Element line = (Element)name.item(0);
                if (line == null) throw new SecurityException("Attenzione, nell'header soap manca il nodo wsse:Username");
                String username = getCharacterDataFromElement(line);
                log.debug("Username: " + username);
                if (username == null) throw new HeaderParserException("Attenzione manca il nodo wsse:Username nell'header SOAP");
                this.username = username;
                NodeList title = element.getElementsByTagName("wsse:Password");
                line = (Element)title.item(0);
                if (line == null) throw new SecurityException("Attenzione, nell'header soap manca il nodo wsse:Password");
                String password = getCharacterDataFromElement(line);
                this.password = password;
                if (password == null) throw new HeaderParserException("Attenzione manca il nodo wsse:Password nell'header SOAP");
                log.debug("Password: " + password);
              }
            }
            catch (Throwable t) {
              String messaggio = Util.stampaDettaglioEccezione(t);
              log.error(messaggio);
              throw new HeaderParserException("Si è verificato un errore nella  lettura dell'header soap, dettaglio errore: " +
                messaggio);
            }
          }

     private String getCharacterDataFromElement(Element e)
      {
        Node child = e.getFirstChild();
        if ((child instanceof CharacterData)) {
          CharacterData cd = (CharacterData)child;
          return cd.getData();
        }
        return "???formato non leggibile???";
      }

}




martedì 7 agosto 2012

Java stampare IP e MAC Address

Importando il package java.net.*;  si può usare un metodo di questo tipo.
Il pattern  %02X dovrebbe significare "scrivi il carattere esadecimale con almeno 2 caratteri".



public static String getMACAddress() {

        InetAddress ip;
        StringBuilder sb = new StringBuilder();

        try {

            ip = InetAddress.getLocalHost();
            System.out.println("Indirizzo IP : " + ip.getHostAddress());

            NetworkInterface network = NetworkInterface.getByInetAddress(ip);

            byte[] mac = network.getHardwareAddress();

            System.out.print("MAC address : ");

            for (int i = 0; i < mac.length; i++) {

                sb.append(String.format("%02X%s", mac[i],
                        (i < mac.length - 1) ? "-" : ""));

            }

        } catch (UnknownHostException e) {

            e.printStackTrace();

        } catch (SocketException e) {

            e.printStackTrace();

        }

        return sb.toString();
    }

lunedì 6 agosto 2012

Ottenere IntialContext da Jboss e WAS

Memo su come ottenere InitialContext

JBOSS

....

import javax.naming.InitialContext;
import javax.naming.Context;
import javax.naming.NamingContext;
public static Context getInitialContext() throws javax.naming.NamingException
{

Properties p=new Properties();
p.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");        
p.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
p.put(Context.PROVIDER_URL, "jnp://127.0.0.1:1099");      
 retutn new InitialContext(p);
}

.....


WAS

....

import javax.naming.InitialContext;
import javax.naming.Context;
import javax.naming.NamingContext;
public static Context getInitialContext() throws javax.naming.NamingException
{

Properties p=new Properties();
p.put(Context.INITIAL_CONTEXT_FACTORY, "com.ibm.ejs.ns.jndi.CNInitialContextFactory");       
p.put(Context.PROVIDER_URL, "iiop:///");      
 retutn new InitialContext(p);
}

.....

domenica 5 agosto 2012

Javascript formattazione date

Questo script mi è stato utile presso un utente che nelle form di inserimento date aveva utilizzava velocemente il tastierino numerico, scrivendo quindi le date senza il separatore "/".
I controlli di congruenza della data venivano sempre svolti lato server, per evitare che il javascript potesse essere "manomesso" e quindi mandare dati sporchi in base dati.
Tuttavia era molto utile per l'utente scrivere le date in fretta e fare in modo che in automatico la procedura mettesse il carattere di separatore.
Lo script è questo:

function aggiustaData(){

var el=document.getElementById("txtData").value;

if(el.length==8){
    var giorno=el.substring(0,2);
    var mese=el.substring(2,4);
    var anno=el.substring(4,8);
    document.getElementById("txtData").value=giorno+"/"+mese+"/"+anno;
    }
    }

L'evento è richiamato all'onblur della textbox e scatta soltanto se la lunghezza del campo è di 8 caratteri.
La textbox è quindi:

<input type="text" name="txtData" id="txtData" size="15" maxlength="10" onblur="aggiustaData()" />

Di seguito l'esempio (digitare la data e poi uscire dalla textbox)


Inserire data :