blob: d54805ec590caf90ddac2ae8f530c957f16710c4 [file] [log] [blame]
/*
* Copyright 2003-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.ws.security.message;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.security.SOAPConstants;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.WSEncryptionPart;
import org.apache.ws.security.WSSecurityException;
import org.apache.ws.security.components.crypto.Crypto;
import org.apache.ws.security.message.token.Reference;
import org.apache.ws.security.message.token.SecurityTokenReference;
import org.apache.ws.security.util.Base64;
import org.apache.ws.security.util.WSSecurityUtil;
import org.apache.xml.security.encryption.EncryptedData;
import org.apache.xml.security.encryption.XMLCipher;
import org.apache.xml.security.encryption.XMLEncryptionException;
import org.apache.xml.security.keys.KeyInfo;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.Vector;
/**
* Encrypts a parts of a message according to WS Specification, X509 profile,
* and adds the encryption data.
*
* @author Davanum Srinivas (dims@yahoo.com).
* @author Werner Dittmann (Werner.Dittmann@apache.org).
*/
public class WSSecEncrypt extends WSSecEncryptedKey {
private static Log log = LogFactory.getLog(WSSecEncrypt.class.getName());
protected String symEncAlgo = WSConstants.AES_128;
protected String encCanonAlgo = null;
protected byte[] embeddedKey = null;
protected String embeddedKeyName = null;
protected boolean useKeyIdentifier;
/**
* Symmetric key used in the EncrytpedKey.
*/
protected SecretKey symmetricKey = null;
/**
* SecurityTokenReference to be inserted into EncryptedData/keyInfo element.
*/
protected SecurityTokenReference securityTokenReference = null;
/**
* Indicates whether to encrypt the symmetric key into an EncryptedKey
* or not.
*/
private boolean encryptSymmKey = true;
/**
* Custom reference value
*/
private String customReferenceValue;
/**
* Constructor.
*/
public WSSecEncrypt() {
}
/**
* Sets the key to use during embedded encryption.
*
* <p/>
*
* @param key
* to use during encryption. The key must fit the selected
* symmetrical encryption algorithm
*/
public void setKey(byte[] key) {
this.embeddedKey = key;
}
/**
* Sets the algorithm to encode the symmetric key.
*
* Default is the <code>WSConstants.KEYTRANSPORT_RSA15</code> algorithm.
*
* @param keyEnc
* specifies the key encoding algorithm.
* @see WSConstants#KEYTRANSPORT_RSA15
* @see WSConstants#KEYTRANSPORT_RSAOEP
*/
public void setKeyEnc(String keyEnc) {
keyEncAlgo = keyEnc;
}
/**
* Set the key name for EMBEDDED_KEYNAME
*
* @param embeddedKeyName
*/
public void setEmbeddedKeyName(String embeddedKeyName) {
this.embeddedKeyName = embeddedKeyName;
}
/**
* Set this true if a key identifier must be used in the KeyInfo
*
* @param useKeyIdentifier
*/
public void setUseKeyIdentifier(boolean useKeyIdentifier) {
this.useKeyIdentifier = useKeyIdentifier;
}
/**
* Set the name of the symmetric encryption algorithm to use.
*
* This encryption algorithm is used to encrypt the data. If the algorithm
* is not set then AES128 is used. Refer to WSConstants which algorithms are
* supported.
*
* @param algo
* Is the name of the encryption algorithm
* @see WSConstants#TRIPLE_DES
* @see WSConstants#AES_128
* @see WSConstants#AES_192
* @see WSConstants#AES_256
*/
public void setSymmetricEncAlgorithm(String algo) {
symEncAlgo = algo;
}
/**
* Set the name of an optional canonicalization algorithm to use before
* encryption.
*
* This c14n algorithm is used to serialize the data before encryption. If
* the algorithm is not set then a standard serialization is used (provided
* by XMLCipher, usually a XMLSerializer according to DOM 3 specification).
*
* @param algo
* Is the name of the canonicalization algorithm
*/
public void setEncCanonicalization(String algo) {
encCanonAlgo = algo;
}
/**
* Get the name of symmetric encryption algorithm to use.
*
* The name of the encryption algorithm to encrypt the data, i.e. the SOAP
* Body. Refer to WSConstants which algorithms are supported.
*
* @return the name of the currently selected symmetric encryption algorithm
* @see WSConstants#TRIPLE_DES
* @see WSConstants#AES_128
* @see WSConstants#AES_192
* @see WSConstants#AES_256
*/
public String getSymmetricEncAlgorithm() {
return symEncAlgo;
}
/**
* Returns if Key Identifiers should be used in KeyInfo
* @return if Key Identifiers should be used in KeyInfo
*/
public boolean getUseKeyIdentifier() {
return useKeyIdentifier;
}
/**
* Initialize a WSSec Encrypt.
*
* The method prepares and initializes a WSSec Encrypt structure after the
* relevant information was set. After preparation of the token references
* can be added and encrypted.
*
* </p>
*
* This method does not add any element to the security header. This must be
* done explicitly.
*
* @param doc
* The SOAP envelope as <code>Document</code>
* @param crypto
* An instance of the Crypto API to handle keystore and
* certificates
* @throws WSSecurityException
*/
public void prepare(Document doc, Crypto crypto) throws WSSecurityException {
document = doc;
/*
* If no external key (symmetricalKey) was set generate an encryption
* key (session key) for this Encrypt element. This key will be
* encrypted using the public key of the receiver
*/
if(this.ephemeralKey == null) {
if (symmetricKey == null) {
KeyGenerator keyGen = getKeyGenerator();
this.symmetricKey = keyGen.generateKey();
}
this.ephemeralKey = this.symmetricKey.getEncoded();
}
if (this.symmetricKey == null) {
this.symmetricKey = WSSecurityUtil.prepareSecretKey(symEncAlgo,
this.ephemeralKey);
}
/*
* Get the certificate that contains the public key for the public key
* algorithm that will encrypt the generated symmetric (session) key.
*/
if(this.encryptSymmKey) {
X509Certificate remoteCert = null;
if (useThisCert != null) {
remoteCert = useThisCert;
} else {
X509Certificate[] certs = crypto.getCertificates(user);
if (certs == null || certs.length <= 0) {
throw new WSSecurityException(
WSSecurityException.FAILURE,
"noUserCertsFound",
new Object[] { user, "encryption" }
);
}
remoteCert = certs[0];
}
prepareInternal(this.ephemeralKey, remoteCert, crypto);
}
}
/**
* Builds the SOAP envelope with encrypted Body and adds encrypted key.
*
* This is a convenience method and for backward compatibility. The method
* calls the single function methods in order to perform a <i>one shot
* encryption</i>. This method is compatible with the build method of the
* previous version with the exception of the additional WSSecHeader
* parameter.
*
* @param doc
* the SOAP envelope as <code>Document</code> with plain text
* Body
* @param crypto
* an instance of the Crypto API to handle keystore and
* Certificates
* @param secHeader
* the security header element to hold the encrypted key element.
* @return the SOAP envelope with encrypted Body as <code>Document
* </code>
* @throws WSSecurityException
*/
public Document build(Document doc, Crypto crypto, WSSecHeader secHeader)
throws WSSecurityException {
doDebug = log.isDebugEnabled();
if (keyIdentifierType == WSConstants.EMBEDDED_KEYNAME
|| keyIdentifierType == WSConstants.EMBED_SECURITY_TOKEN_REF) {
return buildEmbedded(doc, secHeader);
}
if (doDebug) {
log.debug("Beginning Encryption...");
}
prepare(doc, crypto);
if (envelope == null) {
envelope = document.getDocumentElement();
}
SOAPConstants soapConstants = WSSecurityUtil.getSOAPConstants(envelope);
if (parts == null) {
parts = new Vector();
WSEncryptionPart encP = new WSEncryptionPart(soapConstants
.getBodyQName().getLocalPart(), soapConstants
.getEnvelopeURI(), "Content");
parts.add(encP);
}
Element refs = encryptForInternalRef(null, parts);
addInternalRefElement(refs);
prependToHeader(secHeader);
if (bstToken != null) {
prependBSTElementToHeader(secHeader);
}
log.debug("Encryption complete.");
return doc;
}
/**
* Encrypt one or more parts or elements of the message (internal).
*
* This method takes a vector of <code>WSEncryptionPart</code> object that
* contain information about the elements to encrypt. The method call the
* encryption method, takes the reference information generated during
* encryption and add this to the <code>xenc:Reference</code> element.
* This method can be called after <code>prepare()</code> and can be
* called multiple times to encrypt a number of parts or elements.
*
* </p>
*
* The method generates a <code>xenc:Reference</code> element that <i>must</i>
* be added to this token. See <code>addInternalRefElement()</code>.
*
* </p>
*
* If the <code>dataRef</code> parameter is <code>null</code> the method
* creates and initializes a new Reference element.
*
* @param dataRef
* A <code>xenc:Reference</code> element or <code>null</code>
* @param references
* A vector containing WSEncryptionPart objects
* @return Returns the updated <code>xenc:Reference</code> element
* @throws WSSecurityException
*/
public Element encryptForInternalRef(Element dataRef, Vector references)
throws WSSecurityException {
Vector encDataRefs = doEncryption(document, this.symmetricKey,
references);
Element referenceList = dataRef;
if (referenceList == null) {
referenceList = document.createElementNS(WSConstants.ENC_NS,
WSConstants.ENC_PREFIX + ":ReferenceList");
}
createDataRefList(document, referenceList, encDataRefs);
return referenceList;
}
/**
* Encrypt one or more parts or elements of the message (external).
*
* This method takes a vector of <code>WSEncryptionPart</code> object that
* contain information about the elements to encrypt. The method call the
* encryption method, takes the reference information generated during
* encryption and add this to the <code>xenc:Reference</code> element.
* This method can be called after <code>prepare()</code> and can be
* called multiple times to encrypt a number of parts or elements.
*
* </p>
*
* The method generates a <code>xenc:Reference</code> element that <i>must</i>
* be added to the SecurityHeader. See <code>addExternalRefElement()</code>.
*
* </p>
*
* If the <code>dataRef</code> parameter is <code>null</code> the method
* creates and initializes a new Reference element.
*
* @param dataRef
* A <code>xenc:Reference</code> element or <code>null</code>
* @param references
* A vector containing WSEncryptionPart objects
* @return Returns the updated <code>xenc:Reference</code> element
* @throws WSSecurityException
*/
public Element encryptForExternalRef(Element dataRef, Vector references)
throws WSSecurityException {
Vector encDataRefs = doEncryption(document, this.symmetricKey,
references);
Element referenceList = dataRef;
if (referenceList == null) {
referenceList = document.createElementNS(WSConstants.ENC_NS,
WSConstants.ENC_PREFIX + ":ReferenceList");
}
createDataRefList(document, referenceList, encDataRefs);
return referenceList;
}
/**
* Adds the internal Reference element to this Encrypt data.
*
* The reference element <i>must</i> be created by the
* <code>encryptForInternalRef()</code> method. The reference element is
* added to the <code>EncryptedKey</code> element of this encrypt block.
*
* @param dataRef
* The internal <code>enc:Reference</code> element
*/
public void addInternalRefElement(Element dataRef) {
WSSecurityUtil.appendChildElement(document, encryptedKeyElement, dataRef);
}
/**
* Adds (prepends) the external Reference element to the Security header.
*
* The reference element <i>must</i> be created by the
* <code>encryptForExternalRef() </code> method. The method prepends the
* reference element in the SecurityHeader.
*
* @param dataRef
* The external <code>enc:Reference</code> element
* @param secHeader
* The security header.
*/
public void addExternalRefElement(Element dataRef, WSSecHeader secHeader) {
WSSecurityUtil.prependChildElement(document, secHeader
.getSecurityHeader(), dataRef, false);
}
private Vector doEncryption(Document doc, SecretKey secretKey,
Vector references) throws WSSecurityException {
KeyInfo keyInfo = null;
// Prepare KeyInfo if useKeyIdentifier is set
if (useKeyIdentifier &&
keyIdentifierType == WSConstants.ENCRYPTED_KEY_SHA1_IDENTIFIER) {
keyInfo = new KeyInfo(document);
SecurityTokenReference secToken = new SecurityTokenReference(document);
if(this.customReferenceValue != null) {
secToken.setKeyIdentifierEncKeySHA1(this.customReferenceValue);
} else {
secToken.setKeyIdentifierEncKeySHA1(getSHA1(encryptedEphemeralKey));
}
keyInfo.addUnknownElement(secToken.getElement());
Element keyInfoElement = keyInfo.getElement();
keyInfoElement.setAttributeNS(WSConstants.XMLNS_NS, "xmlns:"
+ WSConstants.SIG_PREFIX, WSConstants.SIG_NS);
}
return doEncryption(doc, secretKey, keyInfo, references);
}
private Vector doEncryption(Document doc, SecretKey secretKey,
KeyInfo keyInfo, Vector references) throws WSSecurityException {
XMLCipher xmlCipher = null;
try {
xmlCipher = XMLCipher.getInstance(symEncAlgo);
} catch (XMLEncryptionException e3) {
throw new WSSecurityException(
WSSecurityException.UNSUPPORTED_ALGORITHM, null, null, e3);
}
Vector encDataRef = new Vector();
boolean cloneKeyInfo = false;
for (int part = 0; part < references.size(); part++) {
WSEncryptionPart encPart = (WSEncryptionPart) references.get(part);
String idToEnc = encPart.getId();
String elemName = encPart.getName();
String nmSpace = encPart.getNamespace();
String modifier = encPart.getEncModifier();
/*
* Third step: get the data to encrypt.
*
*/
Element body = null;
if (idToEnc != null) {
body = WSSecurityUtil.findElementById(document
.getDocumentElement(), idToEnc, WSConstants.WSU_NS);
if (body == null) {
body = WSSecurityUtil.findElementById(document
.getDocumentElement(), idToEnc, null);
}
} else {
body = (Element) WSSecurityUtil.findElement(document, elemName,
nmSpace);
}
if (body == null) {
throw new WSSecurityException(WSSecurityException.FAILURE,
"noEncElement", new Object[] { "{" + nmSpace + "}"
+ elemName });
}
boolean content = modifier.equals("Content") ? true : false;
String xencEncryptedDataId = "EncDataId-" + body.hashCode();
encPart.setEncId(xencEncryptedDataId);
cloneKeyInfo = true;
if(keyInfo == null) {
keyInfo = new KeyInfo(document);
SecurityTokenReference secToken = new SecurityTokenReference(document);
Reference ref = new Reference(document);
ref.setURI("#" + encKeyId);
secToken.setReference(ref);
keyInfo.addUnknownElement(secToken.getElement());
Element keyInfoElement = keyInfo.getElement();
keyInfoElement.setAttributeNS(WSConstants.XMLNS_NS, "xmlns:"
+ WSConstants.SIG_PREFIX, WSConstants.SIG_NS);
}
/*
* Forth step: encrypt data, and set necessary attributes in
* xenc:EncryptedData
*/
try {
if (modifier.equals("Header")) {
Element elem = doc.createElementNS(WSConstants.WSSE11_NS,"wsse11:"+WSConstants.ENCRYPTED_HEADER);
WSSecurityUtil.setNamespace(elem, WSConstants.WSSE11_NS, WSConstants.WSSE11_PREFIX);
String wsuPrefix = WSSecurityUtil.setNamespace(elem,
WSConstants.WSU_NS, WSConstants.WSU_PREFIX);
elem.setAttributeNS(WSConstants.WSU_NS, wsuPrefix + ":Id", "EncHeader-" + body.hashCode());
NamedNodeMap map = body.getAttributes();
for (int i = 0 ; i < map.getLength() ; i++) {
Attr attr = (Attr)map.item(i);
if (attr.getNamespaceURI().equals(WSConstants.URI_SOAP11_ENV)
|| attr.getNamespaceURI().equals(WSConstants.URI_SOAP12_ENV)) {
String soapEnvPrefix = WSSecurityUtil.setNamespace(elem,
attr.getNamespaceURI(), "soapevn");
elem.setAttributeNS(attr.getNamespaceURI(), soapEnvPrefix +":"+attr.getLocalName(), attr.getValue());
}
}
xmlCipher.init(XMLCipher.ENCRYPT_MODE, secretKey);
EncryptedData encData = xmlCipher.getEncryptedData();
encData.setId(xencEncryptedDataId);
encData.setKeyInfo(keyInfo);
xmlCipher.doFinal(doc, body, content);
Element encDataElem = WSSecurityUtil.findElementById(document
.getDocumentElement(), xencEncryptedDataId, null);
Node clone = encDataElem.cloneNode(true);
elem.appendChild(clone);
encDataElem.getParentNode().appendChild(elem);
encDataElem.getParentNode().removeChild(encDataElem);
} else {
xmlCipher.init(XMLCipher.ENCRYPT_MODE, secretKey);
EncryptedData encData = xmlCipher.getEncryptedData();
encData.setId(xencEncryptedDataId);
encData.setKeyInfo(keyInfo);
xmlCipher.doFinal(doc, body, content);
}
if(cloneKeyInfo) {
keyInfo = new KeyInfo((Element) keyInfo.getElement()
.cloneNode(true), null);
}
} catch (Exception e2) {
throw new WSSecurityException(
WSSecurityException.FAILED_ENCRYPTION, null, null, e2);
}
encDataRef.add(new String("#" + xencEncryptedDataId));
}
return encDataRef;
}
private Document buildEmbedded(Document doc, WSSecHeader secHeader)
throws WSSecurityException {
doDebug = log.isDebugEnabled();
if (doDebug) {
log.debug("Beginning Encryption embedded...");
}
envelope = doc.getDocumentElement();
envelope.setAttributeNS(WSConstants.XMLNS_NS, "xmlns:"
+ WSConstants.ENC_PREFIX, WSConstants.ENC_NS);
/*
* Second step: generate a symmetric key from the specified key
* (password) for this algorithm, and set the cipher into encryption
* mode.
*/
if (this.symmetricKey == null) {
if (embeddedKey == null) {
throw new WSSecurityException(WSSecurityException.FAILURE,
"noKeySupplied");
}
this.symmetricKey = WSSecurityUtil.prepareSecretKey(symEncAlgo,
embeddedKey);
}
KeyInfo keyInfo = null;
if (this.keyIdentifierType == WSConstants.EMBEDDED_KEYNAME) {
keyInfo = new KeyInfo(doc);
keyInfo
.addKeyName(embeddedKeyName == null ? user
: embeddedKeyName);
} else if (this.keyIdentifierType == WSConstants.EMBED_SECURITY_TOKEN_REF) {
/*
* This means that we want to embed a <wsse:SecurityTokenReference>
* into keyInfo element. If we need this functionality, this.secRef
* MUST be set before calling the build(doc, crypto) method. So if
* secRef is null then throw an exception.
*/
if (this.securityTokenReference == null) {
throw new WSSecurityException(
WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
"You must set keyInfo element, if the keyIdentifier "
+ "== EMBED_SECURITY_TOKEN_REF");
} else {
keyInfo = new KeyInfo(doc);
Element tmpE = securityTokenReference.getElement();
tmpE.setAttributeNS(WSConstants.XMLNS_NS, "xmlns:"
+ tmpE.getPrefix(), tmpE.getNamespaceURI());
keyInfo.addUnknownElement(securityTokenReference.getElement());
}
}
Element keyInfoElement = keyInfo.getElement();
keyInfoElement.setAttributeNS(WSConstants.XMLNS_NS, "xmlns:"
+ WSConstants.SIG_PREFIX, WSConstants.SIG_NS);
SOAPConstants soapConstants = WSSecurityUtil.getSOAPConstants(envelope);
if (parts == null) {
parts = new Vector();
WSEncryptionPart encP = new WSEncryptionPart(soapConstants
.getBodyQName().getLocalPart(), soapConstants
.getEnvelopeURI(), "Content");
parts.add(encP);
}
Vector encDataRefs = doEncryption(doc, this.symmetricKey, keyInfo,
parts);
/*
* At this point data is encrypted with the symmetric key and can be
* referenced via the above Id
*/
/*
* Now we need to setup the wsse:Security header block 1) get (or
* create) the wsse:Security header block 2) The last step sets up the
* reference list that pints to the encrypted data
*/
Element wsseSecurity = secHeader.getSecurityHeader();
Element referenceList = doc.createElementNS(WSConstants.ENC_NS,
WSConstants.ENC_PREFIX + ":ReferenceList");
referenceList = createDataRefList(doc, referenceList, encDataRefs);
WSSecurityUtil.prependChildElement(doc, wsseSecurity, referenceList,
true);
return doc;
}
private KeyGenerator getKeyGenerator() throws WSSecurityException {
KeyGenerator keyGen = null;
try {
/*
* Assume AES as default, so initialize it
*/
keyGen = KeyGenerator.getInstance("AES");
if (symEncAlgo.equalsIgnoreCase(WSConstants.TRIPLE_DES)) {
keyGen = KeyGenerator.getInstance("DESede");
} else if (symEncAlgo.equalsIgnoreCase(WSConstants.AES_128)) {
keyGen.init(128);
} else if (symEncAlgo.equalsIgnoreCase(WSConstants.AES_192)) {
keyGen.init(192);
} else if (symEncAlgo.equalsIgnoreCase(WSConstants.AES_256)) {
keyGen.init(256);
} else {
return null;
}
} catch (NoSuchAlgorithmException e) {
throw new WSSecurityException(
WSSecurityException.UNSUPPORTED_ALGORITHM, null, null, e);
}
return keyGen;
}
/**
* Create DOM subtree for <code>xenc:EncryptedKey</code>
*
* @param doc
* the SOAP envelope parent document
* @param referenceList
* @param encDataRefs
* @return an <code>xenc:EncryptedKey</code> element
*/
public static Element createDataRefList(Document doc,
Element referenceList, Vector encDataRefs) {
for (int i = 0; i < encDataRefs.size(); i++) {
String dataReferenceUri = (String) encDataRefs.get(i);
Element dataReference = doc.createElementNS(WSConstants.ENC_NS,
WSConstants.ENC_PREFIX + ":DataReference");
dataReference.setAttributeNS(null, "URI", dataReferenceUri);
referenceList.appendChild(dataReference);
}
return referenceList;
}
/**
* @return The symmetric key
*/
public SecretKey getSymmetricKey() {
return symmetricKey;
}
/**
* Set the symmetric key to be used for encryption
*
* @param key
*/
public void setSymmetricKey(SecretKey key) {
this.symmetricKey = key;
}
/**
* @return Return the SecurityTokenRefernce
*/
public SecurityTokenReference getSecurityTokenReference() {
return securityTokenReference;
}
/**
* @param reference
*/
public void setSecurityTokenReference(SecurityTokenReference reference) {
securityTokenReference = reference;
}
public boolean isEncryptSymmKey() {
return encryptSymmKey;
}
public void setEncryptSymmKey(boolean encryptSymmKey) {
this.encryptSymmKey = encryptSymmKey;
}
private String getSHA1(byte[] input) throws WSSecurityException {
try {
MessageDigest sha = null;
sha = MessageDigest.getInstance("SHA-1");
sha.reset();
sha.update(input);
byte[] data = sha.digest();
return Base64.encode(data);
} catch (NoSuchAlgorithmException e) {
throw new WSSecurityException(
WSSecurityException.UNSUPPORTED_ALGORITHM, null, null, e);
}
}
public void setCustomReferenceValue(String customReferenceValue) {
this.customReferenceValue = customReferenceValue;
}
}