All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.picketbox.plugins.vault.PicketBoxSecurityVault Maven / Gradle / Ivy

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors. 
 *
 * This 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 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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 this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.picketbox.plugins.vault;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.jboss.logging.Logger;
import org.jboss.security.ErrorCodes;
import org.jboss.security.plugins.PBEUtils;
import org.jboss.security.vault.SecurityVault;
import org.jboss.security.vault.SecurityVaultException;
import org.picketbox.commons.cipher.Base64;
import org.picketbox.util.EncryptionUtil;
import org.picketbox.util.KeyStoreUtil;
import org.picketbox.util.StringUtil;

/**
 * An instance of {@link SecurityVault} that uses
 * a {@link KeyStore} 
 * The shared key just uses a concatenation of a {@link java.uti.UUID}
 * and a keystore alias.
 * 
 * The following options are expected in the {@link SecurityVault#init(Map)} call:
 * ENC_FILE_DIR: the location where the encoded files will be kept. End with "/" or "\" based on your platform
 * KEYSTORE_URL: location where your keystore is located
 * KEYSTORE_PASSWORD: Masked keystore password.  Has to be prepended with MASK-
 * KEYSTORE_ALIAS: Alias where the keypair is located
 * SALT: salt of the masked password. Ensured it is 8 characters in length
 * ITERATION_COUNT: Iteration Count of the masked password.
 * KEY_SIZE: Key size of encryption. Default is 128 bytes.
 * 
 * @author [email protected]
 * @since Aug 12, 2011
 */
public class PicketBoxSecurityVault implements SecurityVault
{
   protected static Logger log = Logger.getLogger(PicketBoxSecurityVault.class);
   
   protected boolean finishedInit = false;

   protected KeyStore keystore = null;
   
   private KeyPair keypair = null;
   
   protected String encryptionAlgorithm = "AES";
   
   protected int keySize = 128;
   
   private char[] keyStorePWD = null;
   
   protected Map theContent= new ConcurrentHashMap();
   
   protected Map sharedKeyMap = new ConcurrentHashMap();
   
   public static final String ENC_FILE_DIR = "ENC_FILE_DIR";
   
   public static final String KEYSTORE_URL = "KEYSTORE_URL";
   
   public static final String KEYSTORE_PASSWORD = "KEYSTORE_PASSWORD";
   
   public static final String KEYSTORE_ALIAS = "KEYSTORE_ALIAS";
   
   public static final String SALT = "SALT";
   
   public static final String ITERATION_COUNT = "ITERATION_COUNT";
   
   public static final String PASS_MASK_PREFIX = "MASK-";
   
   public static final String PUBLIC_CERT = "PUBLIC_CERT";
   
   public static final String KEY_SIZE = "KEY_SIZE"; 

   protected static final String ENCODED_FILE = "ENC.dat";
   protected static final String SHARED_KEY_FILE = "Shared.dat";
   protected static final String ADMIN_KEY = "ADMIN_KEY";
   
   protected String decodedEncFileDir;
   
   protected String LINE_BREAK = "LINE_BREAK";
   
   /*
    * @see org.jboss.security.vault.SecurityVault#init(java.util.Map)
    */
   @SuppressWarnings("unchecked")
   public void init(Map options) throws SecurityVaultException
   {
      if(options == null)
         throw new SecurityVaultException(ErrorCodes.NULL_ARGUMENT + "Options is null");
      
      if(options.isEmpty())
         throw new SecurityVaultException(ErrorCodes.NULL_VALUE + "Options is empty");
      
      String keystoreURL = (String) options.get(KEYSTORE_URL);
      if(keystoreURL == null)
         throw new SecurityVaultException(ErrorCodes.NULL_VALUE + "Null " + KEYSTORE_URL);
      keystoreURL = StringUtil.getSystemPropertyAsString(keystoreURL);
      
      String maskedPassword = (String) options.get(KEYSTORE_PASSWORD);
      if(maskedPassword == null)
         throw new SecurityVaultException(ErrorCodes.NULL_VALUE + "Null masked keystore password");
      if(maskedPassword.startsWith(PASS_MASK_PREFIX) == false)
         throw new SecurityVaultException(ErrorCodes.NULL_VALUE + "Keystore password is not masked");
   
      String salt = (String) options.get(SALT);
      if(salt == null)
         throw new SecurityVaultException(ErrorCodes.NULL_VALUE + "Salt is null");
      
      String iterationCountStr = (String) options.get(ITERATION_COUNT);
      if(iterationCountStr == null)
         throw new SecurityVaultException(ErrorCodes.NULL_VALUE + "Iteration Count is not set");
      int iterationCount = Integer.parseInt(iterationCountStr);
      
      String alias = (String) options.get(KEYSTORE_ALIAS);
      if(alias == null)
         throw new SecurityVaultException(ErrorCodes.NULL_VALUE + "Keystore Alias is null");
      
      String keySizeStr = (String) options.get(KEY_SIZE);
      if(keySizeStr != null)
      {
         keySize = Integer.parseInt(keySizeStr);
      }
      
      String encFileDir = (String) options.get(ENC_FILE_DIR);
      if(encFileDir == null)
         throw new SecurityVaultException(ErrorCodes.NULL_VALUE + "Option ENC_FILE_DIR is missing");

      FileInputStream fis = null, mapFile = null;
      ObjectInputStream ois = null;
      ObjectInputStream mapIS = null;
      try
      {
         decodedEncFileDir = StringUtil.getSystemPropertyAsString(encFileDir);
         if(directoryExists(decodedEncFileDir) == false)
            throw new SecurityVaultException(ErrorCodes.PROCESSING_FAILED + decodedEncFileDir + " does not exist");
         
         if(!(decodedEncFileDir.endsWith("/") || decodedEncFileDir.endsWith("\\")))
         {
            throw new SecurityVaultException(ErrorCodes.WRONG_FORMAT + decodedEncFileDir + "does not end with / or \\");
         }
         if(encodedFileExists(decodedEncFileDir) ==false)
         {
            setUpVault(decodedEncFileDir);
         }
         
         fis = new FileInputStream(decodedEncFileDir + ENCODED_FILE);
         ois = new ObjectInputStream(fis);
         theContent = (Map) ois.readObject();

         mapFile = new FileInputStream(decodedEncFileDir + SHARED_KEY_FILE );
         mapIS = new ObjectInputStream(mapFile);
         
         sharedKeyMap = (Map) mapIS.readObject();
      }
      catch (Exception e)
      { 
         throw new SecurityVaultException(e); 
      }
      finally
      {
    	  safeClose(fis);
    	  safeClose(mapFile);
    	  safeClose(ois);
    	  safeClose(mapIS);
      }

      try
      {
         String keystorePass = decode(maskedPassword, salt, iterationCount);
         keyStorePWD = keystorePass.toCharArray();
         keystore = KeyStoreUtil.getKeyStore(keystoreURL, keystorePass.toCharArray()); 
         keypair = KeyStoreUtil.getPrivateKey(keystore, alias, keystorePass.toCharArray());
      }
      catch (Exception e)
      { 
         throw new SecurityVaultException(ErrorCodes.PROCESSING_FAILED + "Unable to get Keystore:",e);
      }
      log.info("Default Security Vault Implementation Initialized and Ready");
      finishedInit = true;
   }

   /*
    * @see org.jboss.security.vault.SecurityVault#isInitialized()
    */
   public boolean isInitialized()
   {
      return finishedInit;
   }

   /*
    * @see org.jboss.security.vault.SecurityVault#handshake(java.util.Map)
    */
   public byte[] handshake(Map handshakeOptions) throws SecurityVaultException
   {
      if(handshakeOptions == null)
         throw new SecurityVaultException(ErrorCodes.NULL_ARGUMENT + "Options is null");
      
      if(handshakeOptions.isEmpty())
         throw new SecurityVaultException(ErrorCodes.NULL_VALUE + "Options is empty");
      
      String publicCert = (String) handshakeOptions.get(PUBLIC_CERT);
      if(publicCert == null)
         throw new SecurityVaultException(ErrorCodes.NULL_VALUE + "Public Cert Alias is null");
      
      try
      {
         PublicKey publicKey = KeyStoreUtil.getPublicKey(keystore, publicCert, keyStorePWD);
         if(publicKey == null)
            throw new SecurityVaultException(ErrorCodes.NULL_VALUE + 
            		"Could not retrieve Public Key from KeyStore for alias:" + publicCert);
          
      }
      catch (Exception e)
      {
         throw new SecurityVaultException(e);
      } 
       
      
      StringBuilder uuid = new StringBuilder(UUID.randomUUID().toString());
      uuid.append("LINE_BREAK");
      uuid.append(publicCert);
      
      return Base64.encodeBytes(uuid.toString().getBytes()).getBytes();
   }
   
   /*
    * @see org.jboss.security.vault.SecurityVault#keyList()
    */
   public Set keyList() throws SecurityVaultException
   {
      Set keys = theContent.keySet();
      keys.remove(ADMIN_KEY);
      return keys;
   }

   /*
    * @see org.jboss.security.vault.SecurityVault#store(java.lang.String, java.lang.String, char[], byte[])
    */
   public void store(String vaultBlock, String attributeName, char[] attributeValue, byte[] sharedKey)
         throws SecurityVaultException
   {
      
      if(StringUtil.isNullOrEmpty(vaultBlock))
         throw new SecurityVaultException(ErrorCodes.NULL_VALUE + "vaultBlock is null");
      if(StringUtil.isNullOrEmpty(attributeName))
         throw new SecurityVaultException(ErrorCodes.NULL_VALUE + "attributeName is null");
      
      String mapKey = vaultBlock + "_" + attributeName;
      
      sharedKeyMap.put(mapKey, sharedKey);
      
      String av = new String(attributeValue);
      
      //Get Public Key from shared key
      String decodedSharedKey = new String(Base64.decode(new String(sharedKey)));
      int index = decodedSharedKey.indexOf(LINE_BREAK);
      
      if(index < 0)
         throw new SecurityVaultException(ErrorCodes.MISMATCH_SIZE + "Shared Key is invalid");
      
      String alias = decodedSharedKey.substring(index + LINE_BREAK.length());
      
      Certificate cert;
      try
      {
         cert = keystore.getCertificate(alias);
      }
      catch (KeyStoreException e1)
      { 
         throw new SecurityVaultException(ErrorCodes.PROCESSING_FAILED + "Cannot get certificate:",e1);
      }
      
      EncryptionUtil util = new EncryptionUtil(encryptionAlgorithm,keySize);
      try
      {
         byte[] secretKey = theContent.get(ADMIN_KEY);
         
         SecretKeySpec sKeySpec = new SecretKeySpec(secretKey,encryptionAlgorithm);
         byte[] encryptedData = util.encrypt(av.getBytes(), cert.getPublicKey(), sKeySpec);
         theContent.put(mapKey, encryptedData);
      }
      catch (Exception e1)
      { 
         throw new SecurityVaultException(ErrorCodes.PROCESSING_FAILED + "Unable to encrypt data:",e1);
      }
      try
      {
         writeSharedKeyFile(this.decodedEncFileDir);
      }
      catch (IOException e)
      { 
         throw new SecurityVaultException(ErrorCodes.PROCESSING_FAILED + "Unable to write Shared Key File");
      }
      try
      {
         writeEncodedFile(this.decodedEncFileDir);
      }
      catch (IOException e)
      { 
         throw new SecurityVaultException(ErrorCodes.PROCESSING_FAILED + "Unable to write Encoded File");
      }
   }

   /*
    * @see org.jboss.security.vault.SecurityVault#retrieve(java.lang.String, java.lang.String, byte[])
    */
   public char[] retrieve(String vaultBlock, String attributeName, byte[] sharedKey) throws SecurityVaultException
   {
      if(StringUtil.isNullOrEmpty(vaultBlock))
         throw new SecurityVaultException(ErrorCodes.NULL_ARGUMENT + "vaultBlock is null");
      if(StringUtil.isNullOrEmpty(attributeName))
         throw new SecurityVaultException(ErrorCodes.NULL_ARGUMENT + "attributeName is null");
      
      String mapKey = vaultBlock + "_" + attributeName;
      byte[] encryptedValue = theContent.get(mapKey);
       
      
      byte[] fromMap = sharedKeyMap.get(mapKey);
      
      boolean matches = Arrays.equals(sharedKey, fromMap);
      if(matches == false)
         throw new SecurityVaultException(ErrorCodes.VAULT_MISMATCH + 
        		 "Shared Key does not match for vault block:" + vaultBlock + " and attributeName:" + attributeName);
      
      byte[] secretKey = theContent.get(ADMIN_KEY);
       
      SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, encryptionAlgorithm);
      EncryptionUtil encUtil = new EncryptionUtil(encryptionAlgorithm, keySize);
      try
      {
         return (new String(encUtil.decrypt(encryptedValue, keypair, secretKeySpec))).toCharArray();
      }
      catch (Exception e)
      { 
         throw new SecurityVaultException(ErrorCodes.PROCESSING_FAILED + "Decryption of value failed:",e);
      } 
   }
   /**
    * @see org.jboss.security.vault.SecurityVault#exists(String, String)
    */
   public boolean exists(String vaultBlock, String attributeName) throws SecurityVaultException
   { 
      String mapKey = vaultBlock + "_" + attributeName;
      return theContent.get(mapKey) != null;
   }
   
   /*
    * @see org.jboss.security.vault.SecurityVault#remove(java.lang.String, java.lang.String, byte[])
    */
   public boolean remove(String vaultBlock, String attributeName, byte[] sharedKey)
		   throws SecurityVaultException 
   {
	   String mapKey = vaultBlock + "_" + attributeName;
	   try
	   {
		   theContent.remove(mapKey);
	   }
	   catch(Exception e)
	   {
		   return false;
	   }
	   return true;
	}
   
   private String decode(String maskedString, String salt, int iterationCount) throws Exception
   {
      String pbeAlgo = "PBEwithMD5andDES";
      if (maskedString.startsWith(PASS_MASK_PREFIX))
      {
         // Create the PBE secret key 
         SecretKeyFactory factory = SecretKeyFactory.getInstance(pbeAlgo);

         char[] password = "somearbitrarycrazystringthatdoesnotmatter".toCharArray();
         PBEParameterSpec cipherSpec = new PBEParameterSpec(salt.getBytes(), iterationCount);
         PBEKeySpec keySpec = new PBEKeySpec(password);
         SecretKey cipherKey = factory.generateSecret(keySpec);

         maskedString = maskedString.substring(PASS_MASK_PREFIX.length());
         String decodedValue = PBEUtils.decode64(maskedString, pbeAlgo, cipherKey, cipherSpec);

         maskedString = decodedValue;
      }
      return maskedString;
   }
   
   private void setUpVault(String decodedEncFileDir) throws NoSuchAlgorithmException,IOException
   { 
      theContent = new ConcurrentHashMap();
      EncryptionUtil util = new EncryptionUtil(encryptionAlgorithm,keySize);
      SecretKey secretKey = util.generateKey();
      theContent.put(ADMIN_KEY, secretKey.getEncoded()); 
      
      writeEncodedFile(decodedEncFileDir);
      writeSharedKeyFile(decodedEncFileDir);
   }
   
   private void writeEncodedFile(String decodedEncFileDir) throws IOException
   {
	  FileOutputStream fos = null;
	  ObjectOutputStream oos = null;
	  try
	  {
	      fos = new FileOutputStream(decodedEncFileDir + ENCODED_FILE);
	      oos = new ObjectOutputStream(fos);
	      oos.writeObject(theContent);
	  }
	  finally
	  {
		  safeClose(oos);
		  safeClose(fos);
	  }
   }
   
   private void writeSharedKeyFile(String decodedEncFileDir) throws IOException
   {
	   FileOutputStream fos = null;
	   ObjectOutputStream oos = null;
	   try
	   {
		   fos = new FileOutputStream(decodedEncFileDir + SHARED_KEY_FILE);
		   oos = new ObjectOutputStream(fos);
		   oos.writeObject(sharedKeyMap);
	   }
      finally
      {
    	  safeClose(oos);
    	  safeClose(fos);
      } 
   }
   
   private boolean encodedFileExists(String decodedEncFileDir)
   {
      File file = new File(decodedEncFileDir + ENCODED_FILE);
      return file != null && file.exists();
   }
   
   private boolean directoryExists(String dir)
   {
      File file = new File(dir);
      return file != null && file.exists();
   }
   
   private void safeClose(InputStream fis)
   {
      try
      {
         if(fis != null)
         {
            fis.close();
         }
      }
      catch(Exception e)
      {}
   }

   private void safeClose(OutputStream os)
   {
      try
      {
         if(os != null)
         {
            os.close();
         }
      }
      catch(Exception e)
      {}
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy