/*
* 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.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.SecretKey;
import org.apache.xml.security.encryption.EncryptedData;
import org.apache.xml.security.encryption.EncryptedKey;
import org.apache.xml.security.encryption.XMLCipher;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* XML Encryption Util
* <b>Note: </b> This utility is currently using Apache XML Security
* library API. JSR-106 is not yet final. Until that happens,we
* rely on the non-standard API.
*
* @author Anil.Saldhana@redhat.com
* @since Feb 4, 2009
*/
public class XMLEncryptionUtil
{
public static final String CIPHER_DATA_LOCALNAME = "CipherData";
public static final String ENCRYPTED_DATA_LOCALNAME = "EncryptedData";
public static final String ENCRYPTED_KEY_LOCALNAME = "EncryptedKey";
public static final String DS_KEY_INFO = "ds:KeyInfo";
public static final String XMLNS = "http://www.w3.org/2000/xmlns/";
public static String XMLSIG_NS = "http://www.w3.org/2000/09/xmldsig#";
public static String XMLENC_NS = "http://www.w3.org/2001/04/xmlenc#";
private static Map<String,EncryptionAlgorithm> algorithms;
private static class EncryptionAlgorithm
{
EncryptionAlgorithm(String jceName, String xmlSecName, int size)
{
this.jceName = jceName;
this.xmlSecName = xmlSecName;
this.size = size;
}
public String jceName;
public String xmlSecName;
public int size;
}
static
{
algorithms = new HashMap<String, EncryptionAlgorithm>(4);
algorithms.put("aes-128", new EncryptionAlgorithm("AES", XMLCipher.AES_128, 128));
algorithms.put("aes-192", new EncryptionAlgorithm("AES", XMLCipher.AES_192, 192));
algorithms.put("aes-256", new EncryptionAlgorithm("AES", XMLCipher.AES_256, 256));
algorithms.put("tripledes", new EncryptionAlgorithm("TripleDes", XMLCipher.TRIPLEDES, 168));
//Initialize the Apache XML Security Library
org.apache.xml.security.Init.init();
}
/**
* <p>
* Encrypt the Key to be transported
* </p>
* <p>
* Data is encrypted with a SecretKey. Then the key needs to be
* transported to the other end where it is needed for decryption.
* For the Key transport, the SecretKey is encrypted with the
* recipient's public key. At the receiving end, the receiver
* can decrypt the Secret Key using his private key.s
* </p>
* @param document
* @param keyToBeEncrypted Symmetric Key (SecretKey)
* @param keyUsedToEncryptSecretKey Asymmetric Key (Public Key)
* @param keySize Length of the key
* @return
* @throws Exception
*/
public static EncryptedKey encryptKey(Document document,
SecretKey keyToBeEncrypted, PublicKey keyUsedToEncryptSecretKey,
int keySize) throws Exception
{
if(keyToBeEncrypted == null)
throw new IllegalArgumentException("secret key is null");
XMLCipher keyCipher = null;
String pubKeyAlg = keyUsedToEncryptSecretKey.getAlgorithm();
String keyWrapAlgo = getXMLEncryptionURLForKeyUnwrap(pubKeyAlg, keySize);
keyCipher = XMLCipher.getInstance(keyWrapAlgo);
keyCipher.init(XMLCipher.WRAP_MODE, keyUsedToEncryptSecretKey);
return keyCipher.encryptKey(document, keyToBeEncrypted);
}
/**
* Encrypt a document at the root (Use aes-128)
* @param document
* @param secretKey
* @param publicKey
* @param keySize
* @return
* @throws Exception
*/
public static Document encrypt(Document document, SecretKey secretKey, PublicKey publicKey, int keySize)
throws Exception
{
//Encrypt
XMLCipher cipher = XMLCipher.getInstance(algorithms.get("aes-128").xmlSecName);
cipher.init(XMLCipher.ENCRYPT_MODE, secretKey);
//Encrypted Key
EncryptedKey ekey = XMLEncryptionUtil.encryptKey(document, secretKey, publicKey, keySize);
//Encrypted Data
String encryptionAlgorithm = XMLEncryptionUtil.getXMLEncryptionURL(secretKey.getAlgorithm(), keySize);
//Encrypt the Document
cipher = XMLCipher.getInstance(encryptionAlgorithm);
cipher.init(XMLCipher.ENCRYPT_MODE, secretKey);
Document encryptedDoc = cipher.doFinal(document, document.getDocumentElement());
Element encryptedDocRootElement = encryptedDoc.getDocumentElement();
// The EncryptedKey element is added
Element encryptedKeyElement = cipher.martial(document, ekey);
// Outer ds:KeyInfo Element to hold the EncryptionKey
Element sigElement = encryptedDoc.createElementNS(XMLSIG_NS, DS_KEY_INFO);
sigElement.setAttributeNS(XMLNS, "xmlns:ds", XMLSIG_NS);
sigElement.appendChild(encryptedKeyElement);
//Insert the Encrypted key before the CipherData element
NodeList nodeList = encryptedDocRootElement.getElementsByTagNameNS(XMLENC_NS, CIPHER_DATA_LOCALNAME);
if (nodeList == null || nodeList.getLength() == 0)
throw new IllegalStateException("xenc:CipherData Element Missing");
Element cipherDataElement = (Element) nodeList.item(0);
encryptedDocRootElement.insertBefore(sigElement, cipherDataElement);
return encryptedDoc;
}
/**
* Decrypt a document
* @param encryptedDoc
* @param privateKey
* @return
* @throws Exception
*/
public static Document decrypt(Document encryptedDoc, PrivateKey privateKey) throws Exception
{
//First look for enc data
Element docRoot = encryptedDoc.getDocumentElement();
Node dataEL = null;
Node keyEL = null;
if(XMLENC_NS.equals(docRoot.getNamespaceURI())
&& ENCRYPTED_DATA_LOCALNAME.equals(docRoot.getLocalName()))
{
//we found it
dataEL = docRoot;
}
else
{
NodeList childs = docRoot.getElementsByTagNameNS(XMLENC_NS, ENCRYPTED_DATA_LOCALNAME);
if(childs == null || childs.getLength() == 0)
throw new IllegalStateException("Encrypted Data not found");
dataEL = childs.item(0);
}
NodeList keyList = ((Element)dataEL).getElementsByTagNameNS(XMLENC_NS, ENCRYPTED_KEY_LOCALNAME);
if(keyList == null || keyList.getLength() == 0)
throw new IllegalStateException("Encrypted Key not found");
keyEL = keyList.item(0);
if(dataEL == null)
throw new IllegalStateException("Encrypted Data not found");
if(keyEL == null)
throw new IllegalStateException("Encrypted Key not found");
XMLCipher cipher = XMLCipher.getInstance();
cipher.init(XMLCipher.DECRYPT_MODE, null);
EncryptedData encryptedData = cipher.loadEncryptedData(encryptedDoc, (Element)dataEL);
EncryptedKey encryptedKey = cipher.loadEncryptedKey(encryptedDoc, (Element)keyEL);
Document decryptedDoc = null;
if (encryptedData != null && encryptedKey != null)
{
String encAlgoURL = encryptedData.getEncryptionMethod().getAlgorithm();
XMLCipher keyCipher = XMLCipher.getInstance();
keyCipher.init(XMLCipher.UNWRAP_MODE, privateKey);
Key encryptionKey = keyCipher.decryptKey( encryptedKey, encAlgoURL );
cipher = XMLCipher.getInstance();
cipher.init(XMLCipher.DECRYPT_MODE, encryptionKey);
decryptedDoc = cipher.doFinal(encryptedDoc, (Element)dataEL);
}
return decryptedDoc;
}
/**
* From the secret key, get the W3C XML Encryption URL
* @param publicKeyAlgo
* @param keySize
* @return
*/
private static String getXMLEncryptionURLForKeyUnwrap(String publicKeyAlgo, int keySize)
{
if("AES".equals(publicKeyAlgo))
{
switch(keySize)
{
case 192: return XMLCipher.AES_192_KeyWrap;
case 256: return XMLCipher.AES_256_KeyWrap;
default:
return XMLCipher.AES_128_KeyWrap;
}
}
if(publicKeyAlgo.contains("RSA"))
return XMLCipher.RSA_v1dot5;
if(publicKeyAlgo.contains("DES"))
return XMLCipher.TRIPLEDES_KeyWrap;
throw new IllegalArgumentException("unsupported publicKey Algo:" + publicKeyAlgo);
}
/**
* From the secret key, get the W3C XML Encryption URL
* @param secretKey
* @param keySize
* @return
*/
public static String getXMLEncryptionURL(String algo, int keySize)
{
if("AES".equals(algo))
{
switch(keySize)
{
case 192: return XMLCipher.AES_192;
case 256: return XMLCipher.AES_256;
default:
return XMLCipher.AES_128;
}
}
if(algo.contains("RSA"))
return XMLCipher.RSA_v1dot5;
if(algo.contains("DES"))
return XMLCipher.TRIPLEDES_KeyWrap;
throw new IllegalArgumentException("Secret Key with unsupported algo:" + algo);
}
}