blob: 8d409a365751f3e6b2d85d1b58e0b5350fde5c1a [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.wss4j.dom.message;
import java.io.IOException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.wss4j.common.WSEncryptionPart;
import org.apache.wss4j.common.ext.Attachment;
import org.apache.wss4j.common.ext.AttachmentRequestCallback;
import org.apache.wss4j.common.ext.AttachmentResultCallback;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.wss4j.common.util.AttachmentUtils;
import org.apache.wss4j.common.util.XMLUtils;
import org.apache.wss4j.dom.WSConstants;
import org.apache.wss4j.dom.WSDocInfo;
import org.apache.wss4j.dom.WsuIdAllocator;
import org.apache.wss4j.dom.callback.CallbackLookup;
import org.apache.wss4j.dom.callback.DOMCallbackLookup;
import org.apache.wss4j.dom.util.WSSecurityUtil;
import org.apache.xml.security.algorithms.JCEMapper;
import org.apache.xml.security.encryption.AbstractSerializer;
import org.apache.xml.security.encryption.EncryptedData;
import org.apache.xml.security.encryption.Serializer;
import org.apache.xml.security.encryption.TransformSerializer;
import org.apache.xml.security.encryption.XMLCipher;
import org.apache.xml.security.encryption.XMLCipherUtil;
import org.apache.xml.security.encryption.XMLEncryptionException;
import org.apache.xml.security.keys.KeyInfo;
import org.apache.xml.security.stax.ext.XMLSecurityConstants;
import org.apache.xml.security.utils.EncryptionConstants;
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 org.w3c.dom.NodeList;
/**
* A class to encrypt references.
*/
public class Encryptor {
private Document doc;
private WSSecHeader securityHeader;
private WsuIdAllocator idAllocator;
private CallbackLookup callbackLookup;
private CallbackHandler attachmentCallbackHandler;
private boolean storeBytesInAttachment;
private Serializer encryptionSerializer;
private boolean expandXopInclude;
private WSDocInfo wsDocInfo;
public List<String> doEncryption(
KeyInfo keyInfo,
SecretKey secretKey,
String encryptionAlgorithm,
List<WSEncryptionPart> references,
List<Element> attachmentEncryptedDataElements
) throws WSSecurityException {
XMLCipher xmlCipher = null;
try {
if (encryptionSerializer != null) {
xmlCipher = XMLCipher.getInstance(encryptionSerializer, encryptionAlgorithm);
} else {
xmlCipher = XMLCipher.getInstance(encryptionAlgorithm);
}
} catch (XMLEncryptionException ex) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.UNSUPPORTED_ALGORITHM, ex
);
}
List<String> encDataRef = new ArrayList<>();
WSEncryptionPart attachmentEncryptionPart = null;
for (int part = 0; part < references.size(); part++) {
WSEncryptionPart encPart = references.get(part);
if (encPart.getId() != null && encPart.getId().startsWith("cid:")) {
attachmentEncryptionPart = encPart;
continue;
}
//
// Get the data to encrypt.
//
if (callbackLookup == null) {
callbackLookup = new DOMCallbackLookup(doc);
}
List<Element> elementsToEncrypt =
WSSecurityUtil.findElements(encPart, callbackLookup, doc);
if (elementsToEncrypt == null || elementsToEncrypt.isEmpty()) {
if (!encPart.isRequired()) {
continue;
}
throw new WSSecurityException(
WSSecurityException.ErrorCode.FAILURE,
"noEncElement",
new Object[] {"{" + encPart.getNamespace() + "}" + encPart.getName()});
}
if (expandXopInclude) {
for (Element elementToEncrypt : elementsToEncrypt) {
Element encrElement = elementToEncrypt;
// Look for xop:Include Nodes
List<Element> includeElements =
XMLUtils.findElements(elementToEncrypt.getFirstChild(), "Include", WSConstants.XOP_NS);
if (includeElements != null && !includeElements.isEmpty()) {
// See if we already have an expanded Element available (from Signature) that matches the current Element
Element matchingElement = findMatchingExpandedElement(encrElement);
if (matchingElement != null && matchingElement != encrElement) {
// If so then replace the existing Element to encrypt in the SOAP Envelope
encrElement.getParentNode().replaceChild(matchingElement, encrElement);
encrElement = matchingElement;
// We already have an expanded Element, but might need to delete the attachments
for (Element includeElement : includeElements) {
String xopURI = includeElement.getAttributeNS(null, "href");
if (xopURI != null) {
// Delete the attachment
AttachmentRequestCallback attachmentRequestCallback = new AttachmentRequestCallback();
attachmentRequestCallback.setAttachmentId(WSSecurityUtil.getAttachmentId(xopURI));
try {
attachmentCallbackHandler.handle(new Callback[]{attachmentRequestCallback});
} catch (UnsupportedCallbackException | IOException e) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_CHECK, e);
}
}
}
} else {
// Here we didn't find an already expanded Element, so inline the attachment bytes
WSSecurityUtil.inlineAttachments(includeElements, attachmentCallbackHandler, true);
}
}
if (storeBytesInAttachment) {
try {
String id =
encryptElementInAttachment(keyInfo, secretKey, encryptionAlgorithm, encPart, encrElement);
encPart.setEncId(id);
encDataRef.add("#" + id);
} catch (Exception ex) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_ENCRYPTION, ex);
}
} else {
String id =
encryptElement(encrElement, encPart.getEncModifier(), xmlCipher, secretKey, keyInfo);
encPart.setEncId(id);
encDataRef.add("#" + id);
}
}
} else if (storeBytesInAttachment) {
for (Element elementToEncrypt : elementsToEncrypt) {
try {
String id =
encryptElementInAttachment(keyInfo, secretKey, encryptionAlgorithm, encPart, elementToEncrypt);
encPart.setEncId(id);
encDataRef.add("#" + id);
} catch (Exception ex) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.FAILED_ENCRYPTION, ex
);
}
}
} else {
for (Element elementToEncrypt : elementsToEncrypt) {
String id =
encryptElement(elementToEncrypt, encPart.getEncModifier(), xmlCipher, secretKey, keyInfo);
encPart.setEncId(id);
encDataRef.add("#" + id);
}
}
}
if (attachmentEncryptionPart != null) {
encryptAttachment(keyInfo, secretKey, encryptionAlgorithm, attachmentEncryptionPart, encDataRef,
attachmentEncryptedDataElements);
}
return encDataRef;
}
private Element findMatchingExpandedElement(Element element) {
Element matchingElement = null;
if (element.hasAttributeNS(WSConstants.WSU_NS, "Id")) {
String id = element.getAttributeNS(WSConstants.WSU_NS, "Id");
matchingElement = wsDocInfo.getTokenElement(id);
}
if (matchingElement == null && element.hasAttributeNS(null, "Id")) {
String id = element.getAttributeNS(null, "Id");
matchingElement = wsDocInfo.getTokenElement(id);
}
// Check the Elements are the same
if (matchingElement != null && matchingElement.getNamespaceURI().equals(element.getNamespaceURI())
&& matchingElement.getLocalName().equals(element.getLocalName())) {
return matchingElement;
}
return null;
}
private String encryptElementInAttachment(
KeyInfo keyInfo,
SecretKey secretKey,
String encryptionAlgorithm,
WSEncryptionPart encryptionPart,
Element elementToEncrypt
) throws Exception {
String type = EncryptionConstants.TYPE_ELEMENT;
if ("Content".equals(encryptionPart.getEncModifier())) {
type = EncryptionConstants.TYPE_CONTENT;
}
final String attachmentId = idAllocator.createId("", doc);
String encEncryptedDataId = idAllocator.createId("ED-", attachmentId);
if ("Header".equals(encryptionPart.getEncModifier())
&& elementToEncrypt.getParentNode().equals(WSSecurityUtil.getSOAPHeader(doc))) {
createEncryptedHeaderElement(securityHeader, elementToEncrypt, idAllocator);
}
Element encryptedData =
doc.createElementNS(WSConstants.ENC_NS, WSConstants.ENC_PREFIX + ":EncryptedData");
encryptedData.setAttributeNS(null, "Id", encEncryptedDataId);
encryptedData.setAttributeNS(null, "Type", type);
Element encryptionMethod =
doc.createElementNS(WSConstants.ENC_NS, WSConstants.ENC_PREFIX + ":EncryptionMethod");
encryptionMethod.setAttributeNS(null, "Algorithm", encryptionAlgorithm);
encryptedData.appendChild(encryptionMethod);
encryptedData.appendChild(WSSecurityUtil.cloneElement(doc, keyInfo.getElement()));
Element cipherData =
doc.createElementNS(WSConstants.ENC_NS, WSConstants.ENC_PREFIX + ":CipherData");
Element cipherValue =
doc.createElementNS(WSConstants.ENC_NS, WSConstants.ENC_PREFIX + ":CipherValue");
cipherData.appendChild(cipherValue);
encryptedData.appendChild(cipherData);
Cipher cipher = createCipher(encryptionAlgorithm, secretKey);
// Serialize and encrypt the element
AbstractSerializer serializer = new TransformSerializer(true);
byte[] serializedOctets = null;
if (type.equals(EncryptionConstants.TYPE_CONTENT)) {
NodeList children = elementToEncrypt.getChildNodes();
if (null != children) {
serializedOctets = serializer.serializeToByteArray(children);
} else {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_ENCRYPTION,
"Element has no content.");
}
} else {
serializedOctets = serializer.serializeToByteArray(elementToEncrypt);
}
byte[] encryptedBytes = null;
try {
encryptedBytes = cipher.doFinal(serializedOctets);
} catch (IllegalBlockSizeException ibse) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_ENCRYPTION, ibse);
} catch (BadPaddingException bpe) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_ENCRYPTION, bpe);
}
// Now build up to a properly XML Encryption encoded octet stream
byte[] iv = cipher.getIV();
byte[] finalEncryptedBytes = new byte[iv.length + encryptedBytes.length];
System.arraycopy(iv, 0, finalEncryptedBytes, 0, iv.length);
System.arraycopy(encryptedBytes, 0, finalEncryptedBytes, iv.length, encryptedBytes.length);
if ("Content".equals(encryptionPart.getEncModifier())) {
Node child = elementToEncrypt.getFirstChild();
while (child != null) {
Node sibling = child.getNextSibling();
elementToEncrypt.removeChild(child);
child = sibling;
}
elementToEncrypt.appendChild(encryptedData);
} else {
elementToEncrypt.getParentNode().replaceChild(encryptedData, elementToEncrypt);
}
AttachmentUtils.storeBytesInAttachment(cipherValue, doc, attachmentId,
finalEncryptedBytes, attachmentCallbackHandler);
return encEncryptedDataId;
}
private void encryptAttachment(
KeyInfo keyInfo,
SecretKey secretKey,
String encryptionAlgorithm,
WSEncryptionPart attachmentEncryptionPart,
List<String> encDataRef,
List<Element> attachmentEncryptedDataElements
) throws WSSecurityException {
if (attachmentCallbackHandler == null) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.FAILURE,
"empty", new Object[] {"no attachment callbackhandler supplied"}
);
}
AttachmentRequestCallback attachmentRequestCallback = new AttachmentRequestCallback();
String id = AttachmentUtils.getAttachmentId(attachmentEncryptionPart.getId());
attachmentRequestCallback.setAttachmentId(id);
try {
attachmentCallbackHandler.handle(new Callback[]{attachmentRequestCallback});
} catch (Exception e) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.FAILED_ENCRYPTION, e
);
}
String attachmentEncryptedDataType = WSConstants.SWA_ATTACHMENT_ENCRYPTED_DATA_TYPE_CONTENT_ONLY;
if ("Element".equals(attachmentEncryptionPart.getEncModifier())) {
attachmentEncryptedDataType = WSConstants.SWA_ATTACHMENT_ENCRYPTED_DATA_TYPE_COMPLETE;
}
for (Attachment attachment : attachmentRequestCallback.getAttachments()) {
final String attachmentId = attachment.getId();
String encEncryptedDataId = idAllocator.createId("ED-", attachmentId);
encDataRef.add("#" + encEncryptedDataId);
Element encryptedData =
doc.createElementNS(WSConstants.ENC_NS, WSConstants.ENC_PREFIX + ":EncryptedData");
encryptedData.setAttributeNS(null, "Id", encEncryptedDataId);
encryptedData.setAttributeNS(null, "MimeType", attachment.getMimeType());
encryptedData.setAttributeNS(null, "Type", attachmentEncryptedDataType);
Element encryptionMethod =
doc.createElementNS(WSConstants.ENC_NS, WSConstants.ENC_PREFIX + ":EncryptionMethod");
encryptionMethod.setAttributeNS(null, "Algorithm", encryptionAlgorithm);
encryptedData.appendChild(encryptionMethod);
encryptedData.appendChild(WSSecurityUtil.cloneElement(doc, keyInfo.getElement()));
Element cipherData =
doc.createElementNS(WSConstants.ENC_NS, WSConstants.ENC_PREFIX + ":CipherData");
Element cipherReference =
doc.createElementNS(WSConstants.ENC_NS, WSConstants.ENC_PREFIX + ":CipherReference");
cipherReference.setAttributeNS(null, "URI", "cid:" + attachmentId);
Element transforms = doc.createElementNS(WSConstants.ENC_NS, WSConstants.ENC_PREFIX + ":Transforms");
Element transform = doc.createElementNS(WSConstants.SIG_NS, WSConstants.SIG_PREFIX + ":Transform");
transform.setAttributeNS(null, "Algorithm", WSConstants.SWA_ATTACHMENT_CIPHERTEXT_TRANS);
transforms.appendChild(transform);
cipherReference.appendChild(transforms);
cipherData.appendChild(cipherReference);
encryptedData.appendChild(cipherData);
attachmentEncryptedDataElements.add(encryptedData);
Attachment resultAttachment = new Attachment();
resultAttachment.setId(attachmentId);
resultAttachment.setMimeType("application/octet-stream");
Cipher cipher = createCipher(encryptionAlgorithm, secretKey);
Map<String, String> headers = new HashMap<>(attachment.getHeaders());
resultAttachment.setSourceStream(
AttachmentUtils.setupAttachmentEncryptionStream(
cipher, "Element".equals(attachmentEncryptionPart.getEncModifier()),
attachment, headers
)
);
resultAttachment.addHeaders(headers);
AttachmentResultCallback attachmentResultCallback = new AttachmentResultCallback();
attachmentResultCallback.setAttachmentId(attachmentId);
attachmentResultCallback.setAttachment(resultAttachment);
try {
attachmentCallbackHandler.handle(new Callback[]{attachmentResultCallback});
} catch (Exception e) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_ENCRYPTION, e);
}
}
}
private Cipher createCipher(String encryptionAlgorithm, SecretKey secretKey)
throws WSSecurityException {
String jceAlgorithm = JCEMapper.translateURItoJCEID(encryptionAlgorithm);
try {
Cipher cipher = Cipher.getInstance(jceAlgorithm);
int ivLen = JCEMapper.getIVLengthFromURI(encryptionAlgorithm) / 8;
byte[] iv = XMLSecurityConstants.generateBytes(ivLen);
AlgorithmParameterSpec paramSpec =
XMLCipherUtil.constructBlockCipherParameters(encryptionAlgorithm, iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec);
return cipher;
} catch (Exception e) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_ENCRYPTION, e);
}
}
/**
* Encrypt an element.
*/
private String encryptElement(
Element elementToEncrypt,
String modifier,
XMLCipher xmlCipher,
SecretKey secretKey,
KeyInfo keyInfo
) throws WSSecurityException {
boolean content = "Content".equals(modifier);
//
// Encrypt data, and set necessary attributes in xenc:EncryptedData
//
String xencEncryptedDataId = idAllocator.createId("ED-", elementToEncrypt);
try {
if ("Header".equals(modifier)) {
String soapNamespace = WSSecurityUtil.getSOAPNamespace(doc.getDocumentElement());
if (elementToEncrypt.getParentNode().getNamespaceURI().equals(soapNamespace)
&& WSConstants.ELEM_HEADER.equals(elementToEncrypt.getParentNode().getLocalName())) {
createEncryptedHeaderElement(securityHeader, elementToEncrypt, idAllocator);
}
}
xmlCipher.init(XMLCipher.ENCRYPT_MODE, secretKey);
EncryptedData encData = xmlCipher.getEncryptedData();
encData.setId(xencEncryptedDataId);
encData.setKeyInfo(keyInfo);
xmlCipher.doFinal(doc, elementToEncrypt, content);
return xencEncryptedDataId;
} catch (Exception ex) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.FAILED_ENCRYPTION, ex
);
}
}
private static void createEncryptedHeaderElement(
WSSecHeader securityHeader,
Element elementToEncrypt,
WsuIdAllocator idAllocator
) {
Element elem =
elementToEncrypt.getOwnerDocument().createElementNS(
WSConstants.WSSE11_NS, "wsse11:" + WSConstants.ENCRYPTED_HEADER
);
XMLUtils.setNamespace(elem, WSConstants.WSSE11_NS, WSConstants.WSSE11_PREFIX);
String wsuPrefix =
XMLUtils.setNamespace(elem, WSConstants.WSU_NS, WSConstants.WSU_PREFIX);
String headerId = idAllocator.createId("EH-", elementToEncrypt);
elem.setAttributeNS(
WSConstants.WSU_NS, wsuPrefix + ":Id", headerId
);
//
// Add the EncryptedHeader node to the element to be encrypted's parent
// (i.e. the SOAP header). Add the element to be encrypted to the Encrypted
// Header node as well
//
Node parent = elementToEncrypt.getParentNode();
elementToEncrypt = (Element)parent.replaceChild(elem, elementToEncrypt);
elem.appendChild(elementToEncrypt);
if (securityHeader != null) {
NamedNodeMap map = securityHeader.getSecurityHeaderElement().getAttributes();
for (int i = 0; i < map.getLength(); i++) {
Attr attr = (Attr)map.item(i);
if (WSConstants.URI_SOAP11_ENV.equals(attr.getNamespaceURI())
|| WSConstants.URI_SOAP12_ENV.equals(attr.getNamespaceURI())) {
String soapEnvPrefix =
XMLUtils.setNamespace(
elem, attr.getNamespaceURI(), WSConstants.DEFAULT_SOAP_PREFIX
);
elem.setAttributeNS(
attr.getNamespaceURI(),
soapEnvPrefix + ":" + attr.getLocalName(),
attr.getValue()
);
}
}
}
}
public Document getDoc() {
return doc;
}
public void setDoc(Document doc) {
this.doc = doc;
}
public WSSecHeader getSecurityHeader() {
return securityHeader;
}
public void setSecurityHeader(WSSecHeader securityHeader) {
this.securityHeader = securityHeader;
}
public WsuIdAllocator getIdAllocator() {
return idAllocator;
}
public void setIdAllocator(WsuIdAllocator idAllocator) {
this.idAllocator = idAllocator;
}
public CallbackLookup getCallbackLookup() {
return callbackLookup;
}
public void setCallbackLookup(CallbackLookup callbackLookup) {
this.callbackLookup = callbackLookup;
}
public CallbackHandler getAttachmentCallbackHandler() {
return attachmentCallbackHandler;
}
public void setAttachmentCallbackHandler(CallbackHandler attachmentCallbackHandler) {
this.attachmentCallbackHandler = attachmentCallbackHandler;
}
public boolean isStoreBytesInAttachment() {
return storeBytesInAttachment;
}
public void setStoreBytesInAttachment(boolean storeBytesInAttachment) {
this.storeBytesInAttachment = storeBytesInAttachment;
}
public Serializer getEncryptionSerializer() {
return encryptionSerializer;
}
public void setEncryptionSerializer(Serializer encryptionSerializer) {
this.encryptionSerializer = encryptionSerializer;
}
public boolean isExpandXopInclude() {
return expandXopInclude;
}
public void setExpandXopInclude(boolean expandXopInclude) {
this.expandXopInclude = expandXopInclude;
}
public WSDocInfo getWsDocInfo() {
return wsDocInfo;
}
public void setWsDocInfo(WSDocInfo wsDocInfo) {
this.wsDocInfo = wsDocInfo;
}
}