001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.dataformat.xmlsecurity;
018
019 import java.io.ByteArrayOutputStream;
020 import java.io.InputStream;
021 import java.io.OutputStream;
022 import java.security.InvalidKeyException;
023 import java.security.Key;
024 import java.security.NoSuchAlgorithmException;
025 import java.security.spec.InvalidKeySpecException;
026 import java.util.Arrays;
027 import javax.crypto.SecretKeyFactory;
028 import javax.crypto.spec.DESedeKeySpec;
029 import javax.crypto.spec.SecretKeySpec;
030 import javax.xml.transform.dom.DOMSource;
031
032 import org.w3c.dom.Document;
033 import org.w3c.dom.Element;
034 import org.w3c.dom.Node;
035 import org.w3c.dom.traversal.NodeIterator;
036
037 import org.apache.camel.Exchange;
038 import org.apache.camel.spi.DataFormat;
039 import org.apache.camel.util.ExchangeHelper;
040 import org.apache.camel.util.IOHelper;
041 import org.apache.xml.security.encryption.EncryptedData;
042 import org.apache.xml.security.encryption.EncryptedKey;
043 import org.apache.xml.security.encryption.XMLCipher;
044 import org.apache.xml.security.encryption.XMLEncryptionException;
045 import org.apache.xml.security.keys.KeyInfo;
046 import org.apache.xpath.XPathAPI;
047
048 public class XMLSecurityDataFormat implements DataFormat {
049 private String xmlCipherAlgorithm;
050 private byte[] passPhrase;
051 private String secureTag;
052 private boolean secureTagContents;
053
054 public XMLSecurityDataFormat() {
055 this.xmlCipherAlgorithm = XMLCipher.TRIPLEDES;
056 // set a default pass phrase as its required
057 this.passPhrase = "Just another 24 Byte key".getBytes();
058 this.secureTag = "";
059 this.secureTagContents = true;
060 org.apache.xml.security.Init.init();
061 }
062
063 public XMLSecurityDataFormat(String secureTag, boolean secureTagContents) {
064 this();
065 this.setSecureTag(secureTag);
066 this.setSecureTagContents(secureTagContents);
067 }
068
069 public XMLSecurityDataFormat(String secureTag, boolean secureTagContents, byte[] passPhrase) {
070 this();
071 this.setSecureTag(secureTag);
072 this.setSecureTagContents(secureTagContents);
073 this.setPassPhrase(passPhrase);
074 }
075
076 public XMLSecurityDataFormat(String secureTag, boolean secureTagContents, byte[] passPhrase, String xmlCipherAlgorithm) {
077 this();
078 this.setSecureTag(secureTag);
079 this.setSecureTagContents(secureTagContents);
080 this.setPassPhrase(passPhrase);
081 this.setXmlCipherAlgorithm(xmlCipherAlgorithm);
082 }
083
084 public void marshal(Exchange exchange, Object graph, OutputStream stream) throws Exception {
085
086 // Retrieve the message body as input stream
087 InputStream is = exchange.getContext().getTypeConverter().mandatoryConvertTo(InputStream.class, graph);
088 // and covert that to XML
089 Document document = exchange.getContext().getTypeConverter().convertTo(Document.class, exchange, is);
090
091 Key keyEncryptionkey;
092 Key dataEncryptionkey;
093 if (xmlCipherAlgorithm.equals(XMLCipher.TRIPLEDES)) {
094 keyEncryptionkey = generateEncryptionKey("DESede");
095 dataEncryptionkey = generateEncryptionKey("DESede");
096 } else {
097 keyEncryptionkey = generateEncryptionKey("AES");
098 dataEncryptionkey = generateEncryptionKey("AES");
099 }
100
101 XMLCipher keyCipher = XMLCipher.getInstance(generateXmlCipherAlgorithmKeyWrap());
102 keyCipher.init(XMLCipher.WRAP_MODE, keyEncryptionkey);
103
104 XMLCipher xmlCipher = XMLCipher.getInstance(xmlCipherAlgorithm);
105 xmlCipher.init(XMLCipher.ENCRYPT_MODE, dataEncryptionkey);
106
107 if (secureTag.equalsIgnoreCase("")) {
108 embedKeyInfoInEncryptedData(document, keyCipher, xmlCipher, dataEncryptionkey);
109 document = xmlCipher.doFinal(document, document.getDocumentElement());
110 } else {
111 NodeIterator iter = XPathAPI.selectNodeIterator(document, secureTag);
112 Node node;
113 while ((node = iter.nextNode()) != null) {
114 embedKeyInfoInEncryptedData(document, keyCipher, xmlCipher, dataEncryptionkey);
115 Document temp = xmlCipher.doFinal(document, (Element) node, getSecureTagContents());
116 document.importNode(temp.getDocumentElement().cloneNode(true), true);
117 }
118 }
119
120 try {
121 DOMSource source = new DOMSource(document);
122 InputStream sis = exchange.getContext().getTypeConverter().mandatoryConvertTo(InputStream.class, source);
123 IOHelper.copy(sis, stream);
124 } finally {
125 stream.close();
126 }
127
128 }
129
130 public Object unmarshal(Exchange exchange, InputStream stream) throws Exception {
131 InputStream is = ExchangeHelper.getMandatoryInBody(exchange, InputStream.class);
132
133 Key keyEncryptionkey;
134 if (xmlCipherAlgorithm.equals(XMLCipher.TRIPLEDES)) {
135 keyEncryptionkey = generateEncryptionKey("DESede");
136 } else {
137 keyEncryptionkey = generateEncryptionKey("AES");
138 }
139
140 XMLCipher xmlCipher = XMLCipher.getInstance();
141 xmlCipher.init(XMLCipher.DECRYPT_MODE, null);
142 xmlCipher.setKEK(keyEncryptionkey);
143
144 Document encodedDocument = exchange.getContext().getTypeConverter().convertTo(Document.class, exchange, is);
145
146 if (secureTag.equalsIgnoreCase("")) {
147 encodedDocument = xmlCipher.doFinal(encodedDocument, encodedDocument.getDocumentElement());
148 } else {
149 NodeIterator iter =
150 XPathAPI.selectNodeIterator(encodedDocument, secureTag);
151 Node node;
152 while ((node = iter.nextNode()) != null) {
153 Document temp = xmlCipher.doFinal(encodedDocument, (Element) node, getSecureTagContents());
154 encodedDocument.importNode(temp.getDocumentElement().cloneNode(true), true);
155 }
156 }
157
158 ByteArrayOutputStream bos = new ByteArrayOutputStream();
159 try {
160 DOMSource source = new DOMSource(encodedDocument);
161 InputStream sis = exchange.getContext().getTypeConverter().mandatoryConvertTo(InputStream.class, source);
162 IOHelper.copy(sis, bos);
163 } finally {
164 bos.close();
165 }
166
167 // Return the decrypted data
168 return bos.toByteArray();
169 }
170
171 private Key generateEncryptionKey(String algorithm) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException {
172 DESedeKeySpec keySpec;
173 Key secretKey;
174 try {
175 if (algorithm.equalsIgnoreCase("DESede")) {
176 keySpec = new DESedeKeySpec(passPhrase);
177 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
178 secretKey = keyFactory.generateSecret(keySpec);
179 } else {
180 secretKey = new SecretKeySpec(passPhrase, "AES");
181 }
182 } catch (InvalidKeyException e) {
183 throw new InvalidKeyException("InvalidKeyException due to invalid passPhrase: " + Arrays.toString(passPhrase));
184 } catch (NoSuchAlgorithmException e) {
185 throw new NoSuchAlgorithmException("NoSuchAlgorithmException while using XMLCipher.TRIPLEDES algorithm: DESede");
186 } catch (InvalidKeySpecException e) {
187 throw new InvalidKeySpecException("Invalid Key generated while using passPhrase: " + Arrays.toString(passPhrase));
188 }
189 return secretKey;
190 }
191
192 private void embedKeyInfoInEncryptedData(Document document, XMLCipher keyCipher, XMLCipher xmlCipher, Key dataEncryptionkey) throws XMLEncryptionException {
193 EncryptedKey encryptedKey = keyCipher.encryptKey(document, dataEncryptionkey);
194 KeyInfo keyInfo = new KeyInfo(document);
195 keyInfo.add(encryptedKey);
196 EncryptedData encryptedDataElement = xmlCipher.getEncryptedData();
197 encryptedDataElement.setKeyInfo(keyInfo);
198 }
199
200 private String generateXmlCipherAlgorithmKeyWrap() {
201 String algorithmKeyWrap = null;
202 if (xmlCipherAlgorithm.equalsIgnoreCase(XMLCipher.TRIPLEDES)) {
203 algorithmKeyWrap = XMLCipher.TRIPLEDES_KeyWrap;
204 } else if (xmlCipherAlgorithm.equalsIgnoreCase(XMLCipher.AES_128)) {
205 algorithmKeyWrap = XMLCipher.AES_128_KeyWrap;
206 } else if (xmlCipherAlgorithm.equalsIgnoreCase(XMLCipher.AES_192)) {
207 algorithmKeyWrap = XMLCipher.AES_192_KeyWrap;
208 } else if (xmlCipherAlgorithm.equalsIgnoreCase(XMLCipher.AES_256)) {
209 algorithmKeyWrap = XMLCipher.AES_256_KeyWrap;
210 }
211
212 return algorithmKeyWrap;
213 }
214
215 public String getXmlCipherAlgorithm() {
216 return xmlCipherAlgorithm;
217 }
218
219 public void setXmlCipherAlgorithm(String xmlCipherAlgorithm) {
220 this.xmlCipherAlgorithm = xmlCipherAlgorithm;
221 }
222
223 public byte[] getPassPhrase() {
224 return passPhrase;
225 }
226
227 public void setPassPhrase(byte[] passPhrase) {
228 this.passPhrase = passPhrase;
229 }
230
231 public String getSecureTag() {
232 return secureTag;
233 }
234
235 public void setSecureTag(String secureTag) {
236 this.secureTag = secureTag;
237 }
238
239 public boolean isSecureTagContents() {
240 return secureTagContents;
241 }
242
243 public boolean getSecureTagContents() {
244 return secureTagContents;
245 }
246
247 public void setSecureTagContents(boolean secureTagContents) {
248 this.secureTagContents = secureTagContents;
249 }
250
251 }