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    }