/*
* 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.jboss.security.integration.password;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import org.apache.log4j.Logger;
import org.jboss.security.plugins.FilePassword;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/**
* Manages masking the password for xml configuration files
* @author Anil.Saldhana@redhat.com
* @since Mar 26, 2009
*/
public class PasswordMaskManagement
{
private Logger log = Logger.getLogger(PasswordMaskManagement.class);
private Map<String,char[]> passwordMap = new HashMap<String,char[]>();
private KeyStore keystore;
private String alias = "jboss";
private String passwordEncryptedFileName = "password/jboss_password_enc.dat";
static String keystorePassEncFileName = "password/jboss_keystore_pass.dat";
private String keystoreLocation = "password/password.keystore";
KeyPair kp = null;
private char[] storePass;
public PasswordMaskManagement()
{
}
//Public Methods
public void setKeyStoreDetails(String location, String alias) throws Exception
{
if(location == null)
throw new IllegalArgumentException("location is null");
this.keystoreLocation = location;
this.alias = alias;
this.ensureKeyStore();
}
public void setKeyStoreDetails(String location, char[] storePass, String alias) throws Exception
{
if(location == null)
throw new IllegalArgumentException("location is null");
this.keystore = KeyStoreUtil.getKeyStore(location, storePass);
this.storePass = storePass;
this.alias = alias;
load();
}
//Public property setters
public void setKeyStoreLocation(String location)
{
if(location == null)
throw new IllegalArgumentException("location is null");
this.keystoreLocation = location;
}
public void setKeyStoreAlias(String alias)
{
if(alias == null)
throw new IllegalArgumentException("alias is null");
this.alias = alias;
}
/**
* Customize the location where the encrypted
* password file needs to be stored
* @param pefn
*/
public void setPasswordEncryptedFileName(String pefn)
{
this.passwordEncryptedFileName = pefn;
}
/**
* Customize the location where the encrypted
* keystore password file is stored
* @param kpe
*/
public void setKeyStorePasswordEncryptedFileName(String kpe)
{
keystorePassEncFileName = kpe;
}
//Package protected Methods
/**
* Whether a security domain exists
* in the password map
* @param securityDomain
* @return
*/
boolean exists(String securityDomain)
{
return this.passwordMap.containsKey(securityDomain);
}
/**
* Check whether the keystore exists
* @return
*/
boolean keyStoreExists()
{
return this.keystore != null;
}
/**
* Get the password
* @param securityDomain
* @return
* @throws Exception
*/
char[] getPassword(String securityDomain) throws Exception
{
if(keystore == null)
{
if(this.storePass == null)
this.ensureKeyStore();
if(passwordMap.size() == 0)
load();
}
return passwordMap.get(securityDomain);
}
void storePassword(String securityDomain, char[] pass)
{
this.passwordMap.put(securityDomain, pass);
}
void removePassword(String domainToRemove)
{
this.passwordMap.remove(domainToRemove);
}
void load() throws Exception
{
Document doc = loadPasswordEncryptedDocument();
if(doc == null)
{
log.trace(this.passwordEncryptedFileName + " does not exist");
return;
}
if(keystore == null)
{
System.out.println("Keystore is null. Please specify keystore below:");
return;
}
PrivateKey privateKey = (PrivateKey) keystore.getKey(this.alias, this.storePass);
if(privateKey == null)
throw new IllegalStateException("private key not found");
Document decryptedDoc = XMLEncryptionUtil.decrypt(doc, privateKey);
NodeList nl = decryptedDoc.getDocumentElement().getElementsByTagName("entry");
int len = nl != null ? nl.getLength() : 0;
System.out.println("Loading domains [");
for(int i = 0; i < len; i++)
{
Element n = (Element) nl.item(i);
String name = n.getAttribute("name");
System.out.println(name + ",");
this.passwordMap.put(name, n.getAttribute("pass").toCharArray());
}
System.out.println("]");
}
void store() throws Exception
{
if(this.keystore == null)
{
System.out.println("Keystore is null. Cannot store.");
return;
}
StringBuilder builder = new StringBuilder();
Document doc = DocumentUtil.createDocument();
Element el = doc.createElementNS(null, "pass-map");
doc.appendChild(el);
System.out.println("Storing domains [");
Set<Entry<String,char[]>> entries = this.passwordMap.entrySet();
for(Entry<String,char[]> e: entries)
{
Element entry = doc.createElementNS(null, "entry");
System.out.println(e.getKey()+",");
entry.setAttributeNS(null, "name", e.getKey());
entry.setAttributeNS(null, "pass", new String(e.getValue()));
el.appendChild(entry);
}
builder.append("</pass-map>");
System.out.println("]");
SecretKey skey = this.getSecretKey("AES", 128);
PublicKey pk = KeyStoreUtil.getPublicKey(keystore, alias, storePass);
if(pk == null)
throw new RuntimeException("public key is null");
XMLEncryptionUtil.encrypt(doc, skey, pk, 128);
storePasswordEncryptedDocument(doc);
}
void ensurePasswordFile() throws Exception
{
try
{
this.loadPasswordEncryptedDocument();
}
catch(FileNotFoundException e)
{
//Just create the file
File file = new File(passwordEncryptedFileName);
if(file.exists() == false)
file.createNewFile();
}
}
void ensureKeyStore() throws Exception
{
if(keystore == null)
{
if(keystoreLocation == null)
throw new IllegalStateException("KeyStore Location is null");
ClassLoader tcl = SecurityActions.getContextClassLoader();
/**
* Look for the encrypted keystore pass file
* via the direct File approach or via the context classloader
*/
URL keyEncFileURL = null;
File keyfile = new File(keystorePassEncFileName);
if(keyfile.exists() == false)
{
keyEncFileURL = tcl.getResource(keystorePassEncFileName);
}
else
keyEncFileURL = keyfile.toURL();
//Get the keystore passwd
FilePassword fp = null;
try
{
fp = new FilePassword(keyEncFileURL.toString());
this.storePass = fp.toCharArray();
}
catch(IOException eof)
{
throw new IllegalStateException("The Keystore Encrypted file not located:",eof);
}
if(this.storePass == null)
throw new IllegalStateException("Keystore password is null");
/**
* We look for the keystore in either direct File access or
* via the context class loader
*/
URL ksFileURL = null;
File ksFile = new File(keystoreLocation);
if(ksFile.exists() == false)
ksFileURL = tcl.getResource(keystoreLocation);
else
ksFileURL = ksFile.toURL();
this.keystore = KeyStoreUtil.getKeyStore(ksFileURL, storePass);
}
}
/**
* Generate a secret key useful for encryption/decryption
* @param encAlgo
* @param keySize Length of the key (if 0, defaults to 128 bits)
* @return
* @throws Exception
*/
private SecretKey getSecretKey(String encAlgo, int keySize) throws Exception
{
KeyGenerator keyGenerator = KeyGenerator.getInstance(encAlgo);
if(keySize == 0)
keySize = 128;
keyGenerator.init(keySize);
return keyGenerator.generateKey();
}
private Document loadPasswordEncryptedDocument() throws Exception
{
Document doc = null;
File docFile = new File(this.passwordEncryptedFileName);
if(docFile == null || docFile.exists() == false)
{
//Try the TCL
ClassLoader tcl = SecurityActions.getContextClassLoader();
InputStream is = tcl.getResourceAsStream(passwordEncryptedFileName);
if(is == null)
throw new FileNotFoundException("Encrypted password file not located");
doc = DocumentUtil.getDocument(is);
}
else
{
doc = DocumentUtil.getDocument(docFile);
}
return doc;
}
private void storePasswordEncryptedDocument(Document doc) throws Exception
{
byte[] data = DocumentUtil.getDocumentAsString(doc).getBytes();
FileOutputStream faos = null;
//Try the url route
try
{
URL url = new URL(this.passwordEncryptedFileName);
File file = new File(url.toString());
faos = new FileOutputStream(file);
faos.write(data);
faos.flush();
faos.close();
}
catch(Exception e)
{
if(faos == null)
faos = new FileOutputStream(new File(passwordEncryptedFileName));
}
finally
{
if(faos == null)
throw new RuntimeException("File Output Stream is null");
faos.write(data);
faos.flush();
faos.close();
}
}
}