View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied. See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.ws.security.processor;
21  
22  import java.security.PrivateKey;
23  import java.security.cert.X509Certificate;
24  import java.security.spec.MGF1ParameterSpec;
25  import java.util.ArrayList;
26  import java.util.LinkedList;
27  import java.util.List;
28  
29  import javax.crypto.Cipher;
30  import javax.crypto.KeyGenerator;
31  import javax.crypto.SecretKey;
32  import javax.crypto.spec.OAEPParameterSpec;
33  import javax.crypto.spec.PSource;
34  
35  import org.apache.ws.security.WSConstants;
36  import org.apache.ws.security.WSDataRef;
37  import org.apache.ws.security.WSDocInfo;
38  import org.apache.ws.security.WSSecurityEngineResult;
39  import org.apache.ws.security.WSSecurityException;
40  import org.apache.ws.security.components.crypto.AlgorithmSuite;
41  import org.apache.ws.security.components.crypto.AlgorithmSuiteValidator;
42  import org.apache.ws.security.components.crypto.Crypto;
43  import org.apache.ws.security.components.crypto.CryptoType;
44  import org.apache.ws.security.handler.RequestData;
45  import org.apache.ws.security.message.token.SecurityTokenReference;
46  import org.apache.ws.security.str.EncryptedKeySTRParser;
47  import org.apache.ws.security.str.STRParser;
48  import org.apache.ws.security.util.Base64;
49  import org.apache.ws.security.util.WSSecurityUtil;
50  import org.apache.xml.security.algorithms.JCEMapper;
51  import org.w3c.dom.Document;
52  import org.w3c.dom.Element;
53  import org.w3c.dom.Node;
54  import org.w3c.dom.Text;
55  
56  public class EncryptedKeyProcessor implements Processor {
57      private static org.apache.commons.logging.Log log = 
58          org.apache.commons.logging.LogFactory.getLog(EncryptedKeyProcessor.class);
59      
60      public List<WSSecurityEngineResult> handleToken(
61          Element elem, 
62          RequestData data,
63          WSDocInfo wsDocInfo
64      ) throws WSSecurityException {
65          return handleToken(elem, data, wsDocInfo, data.getAlgorithmSuite());
66      }
67      
68      public List<WSSecurityEngineResult> handleToken(
69          Element elem, 
70          RequestData data,
71          WSDocInfo wsDocInfo,
72          AlgorithmSuite algorithmSuite
73      ) throws WSSecurityException {
74          if (log.isDebugEnabled()) {
75              log.debug("Found encrypted key element");
76          }
77          if (data.getDecCrypto() == null) {
78              throw new WSSecurityException(WSSecurityException.FAILURE, "noDecCryptoFile");
79          }
80          if (data.getCallbackHandler() == null) {
81              throw new WSSecurityException(WSSecurityException.FAILURE, "noCallback");
82          }
83          //
84          // lookup xenc:EncryptionMethod, get the Algorithm attribute to determine
85          // how the key was encrypted. Then check if we support the algorithm
86          //
87          String encryptedKeyTransportMethod = X509Util.getEncAlgo(elem);
88          if (encryptedKeyTransportMethod == null) {
89              throw new WSSecurityException(
90                  WSSecurityException.UNSUPPORTED_ALGORITHM, "noEncAlgo"
91              );
92          }
93          if (data.getWssConfig().isWsiBSPCompliant()) {
94              checkBSPCompliance(elem, encryptedKeyTransportMethod);
95          }
96          Cipher cipher = WSSecurityUtil.getCipherInstance(encryptedKeyTransportMethod);
97          //
98          // Now lookup CipherValue.
99          //
100         Element tmpE = 
101             WSSecurityUtil.getDirectChildElement(
102                 elem, "CipherData", WSConstants.ENC_NS
103             );
104         Element xencCipherValue = null;
105         if (tmpE != null) {
106             xencCipherValue = 
107                 WSSecurityUtil.getDirectChildElement(tmpE, "CipherValue", WSConstants.ENC_NS);
108         }
109         if (xencCipherValue == null) {
110             throw new WSSecurityException(WSSecurityException.INVALID_SECURITY, "noCipher");
111         }
112         
113         STRParser strParser = new EncryptedKeySTRParser();
114         X509Certificate[] certs = 
115             getCertificatesFromEncryptedKey(elem, data, data.getDecCrypto(), wsDocInfo, strParser);
116 
117         // Check for compliance against the defined AlgorithmSuite
118         if (algorithmSuite != null) {
119             AlgorithmSuiteValidator algorithmSuiteValidator = new
120                 AlgorithmSuiteValidator(algorithmSuite);
121 
122             algorithmSuiteValidator.checkAsymmetricKeyLength(certs[0]);
123             algorithmSuiteValidator.checkEncryptionKeyWrapAlgorithm(
124                 encryptedKeyTransportMethod
125             );
126         }
127         
128         try {
129             PrivateKey privateKey = data.getDecCrypto().getPrivateKey(certs[0], data.getCallbackHandler());
130             OAEPParameterSpec oaepParameterSpec = null;
131             if (WSConstants.KEYTRANSPORT_RSAOEP.equals(encryptedKeyTransportMethod)) {
132                 // Get the DigestMethod if it exists
133                 String digestAlgorithm = getDigestAlgorithm(elem);
134                 String jceDigestAlgorithm = "SHA-1";
135                 if (digestAlgorithm != null && !"".equals(digestAlgorithm)) {
136                     jceDigestAlgorithm = JCEMapper.translateURItoJCEID(digestAlgorithm);
137                 }
138                 
139                 oaepParameterSpec = 
140                     new OAEPParameterSpec(
141                         jceDigestAlgorithm, "MGF1", new MGF1ParameterSpec("SHA-1"), PSource.PSpecified.DEFAULT
142                     );
143             }
144             if (oaepParameterSpec == null) {
145                 cipher.init(Cipher.DECRYPT_MODE, privateKey);
146             } else {
147                 cipher.init(Cipher.DECRYPT_MODE, privateKey, oaepParameterSpec);
148             }
149         } catch (Exception ex) {
150             throw new WSSecurityException(WSSecurityException.FAILED_CHECK, null, null, ex);
151         }
152         
153         List<String> dataRefURIs = getDataRefURIs(elem);
154         
155         byte[] encryptedEphemeralKey = null;
156         byte[] decryptedBytes = null;
157         try {
158             encryptedEphemeralKey = getDecodedBase64EncodedData(xencCipherValue);
159             decryptedBytes = cipher.doFinal(encryptedEphemeralKey);
160         } catch (IllegalStateException ex) {
161             throw new WSSecurityException(WSSecurityException.FAILED_CHECK, null, null, ex);
162         } catch (Exception ex) {
163             decryptedBytes = getRandomKey(dataRefURIs, elem.getOwnerDocument(), wsDocInfo);
164         }
165 
166         List<WSDataRef> dataRefs = decryptDataRefs(dataRefURIs, elem.getOwnerDocument(), wsDocInfo,
167             decryptedBytes, data);
168         
169         WSSecurityEngineResult result = new WSSecurityEngineResult(
170                 WSConstants.ENCR, 
171                 decryptedBytes,
172                 encryptedEphemeralKey,
173                 dataRefs,
174                 certs
175             );
176         result.put(
177             WSSecurityEngineResult.TAG_ENCRYPTED_KEY_TRANSPORT_METHOD, 
178             encryptedKeyTransportMethod
179         );
180         result.put(WSSecurityEngineResult.TAG_ID, elem.getAttributeNS(null, "Id"));
181         result.put(WSSecurityEngineResult.TAG_X509_REFERENCE_TYPE, strParser.getCertificatesReferenceType());
182         wsDocInfo.addResult(result);
183         wsDocInfo.addTokenElement(elem);
184         return java.util.Collections.singletonList(result);
185     }
186     
187     /**
188      * Generates a random secret key using the algorithm specified in the
189      * first DataReference URI
190      * 
191      * @param dataRefURIs
192      * @param doc
193      * @param wsDocInfo
194      * @return
195      * @throws WSSecurityException
196      */
197     private static byte[] getRandomKey(List<String> dataRefURIs, Document doc, WSDocInfo wsDocInfo) throws WSSecurityException {
198         try {
199             String alg = "AES";
200             int size = 128;
201             if (!dataRefURIs.isEmpty()) {
202                 String uri = dataRefURIs.iterator().next();
203                 Element ee = ReferenceListProcessor.findEncryptedDataElement(doc, wsDocInfo, uri);
204                 String algorithmURI = X509Util.getEncAlgo(ee);
205                 alg = JCEMapper.getJCEKeyAlgorithmFromURI(algorithmURI);
206                 size = WSSecurityUtil.getKeyLength(algorithmURI);
207             }
208             KeyGenerator kgen = KeyGenerator.getInstance(alg);
209             kgen.init(size * 8);
210             SecretKey k = kgen.generateKey();
211             return k.getEncoded();
212         } catch (Exception ex) {
213             throw new WSSecurityException(WSSecurityException.FAILED_CHECK, null, null, ex);
214         }
215     }
216     
217     /**
218      * Method getDecodedBase64EncodedData
219      *
220      * @param element
221      * @return a byte array containing the decoded data
222      * @throws WSSecurityException
223      */
224     private static byte[] getDecodedBase64EncodedData(Element element) throws WSSecurityException {
225         StringBuilder sb = new StringBuilder();
226         Node node = element.getFirstChild();
227         while (node != null) {
228             if (Node.TEXT_NODE == node.getNodeType()) {
229                 sb.append(((Text) node).getData());
230             }
231             node = node.getNextSibling();
232         }
233         String encodedData = sb.toString();
234         return Base64.decode(encodedData);
235     }
236     
237     private static String getDigestAlgorithm(Node encBodyData) throws WSSecurityException {
238         Element tmpE = 
239             WSSecurityUtil.getDirectChildElement(
240                 encBodyData, "EncryptionMethod", WSConstants.ENC_NS
241             );
242         if (tmpE != null) {
243             Element digestElement = 
244                 WSSecurityUtil.getDirectChildElement(tmpE, "DigestMethod", WSConstants.SIG_NS);
245             if (digestElement != null) {
246                 return digestElement.getAttribute("Algorithm");
247             }
248         }
249         return null;
250     }
251     
252     /**
253      * @return the Certificate(s) corresponding to the public key reference in the 
254      * EncryptedKey Element
255      */
256     private X509Certificate[] getCertificatesFromEncryptedKey(
257         Element xencEncryptedKey,
258         RequestData data,
259         Crypto crypto,
260         WSDocInfo wsDocInfo,
261         STRParser strParser
262     ) throws WSSecurityException {
263         Element keyInfo = 
264             WSSecurityUtil.getDirectChildElement(
265                 xencEncryptedKey, "KeyInfo", WSConstants.SIG_NS
266             );
267         if (keyInfo != null) {
268             Element strElement = null;
269             if (data.getWssConfig().isWsiBSPCompliant()) {
270                 int result = 0;
271                 Node node = keyInfo.getFirstChild();
272                 while (node != null) {
273                     if (Node.ELEMENT_NODE == node.getNodeType()) {
274                         result++;
275                         strElement = (Element)node;
276                     }
277                     node = node.getNextSibling();
278                 }
279                 if (result != 1) {
280                     throw new WSSecurityException(
281                         WSSecurityException.INVALID_SECURITY, "invalidDataRef"
282                     );
283                 }
284             } else {
285                  strElement = 
286                     WSSecurityUtil.getDirectChildElement(
287                         keyInfo,
288                         SecurityTokenReference.SECURITY_TOKEN_REFERENCE,
289                         WSConstants.WSSE_NS
290                     );
291             }
292             if (strElement == null || strParser == null) {
293                 throw new WSSecurityException(
294                     WSSecurityException.INVALID_SECURITY, "noSecTokRef"
295                 );
296             }
297             strParser.parseSecurityTokenReference(strElement, data, wsDocInfo, null);
298             
299             X509Certificate[] certs = strParser.getCertificates();
300             if (certs == null || certs.length < 1 || certs[0] == null) {
301                 throw new WSSecurityException(
302                     WSSecurityException.FAILURE,
303                     "noCertsFound", 
304                     new Object[] {"decryption (KeyId)"}
305                 );
306             }
307             return certs;
308         } else if (!data.getWssConfig().isWsiBSPCompliant() 
309             && crypto.getDefaultX509Identifier() != null) {
310             String alias = crypto.getDefaultX509Identifier();
311             CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
312             cryptoType.setAlias(alias);
313             X509Certificate[] certs = crypto.getX509Certificates(cryptoType);
314             if (certs == null || certs.length < 1 || certs[0] == null) {
315                 throw new WSSecurityException(
316                     WSSecurityException.FAILURE,
317                     "noCertsFound", 
318                     new Object[] {"decryption (KeyId)"}
319                 );
320             }
321             return certs;
322         } else {
323             throw new WSSecurityException(WSSecurityException.INVALID_SECURITY, "noKeyinfo");
324         }
325     }
326     
327     /**
328      * Find the list of all URIs that this encrypted Key references
329      */
330     private List<String> getDataRefURIs(Element xencEncryptedKey) {
331         // Lookup the references that are encrypted with this key
332         Element refList = 
333             WSSecurityUtil.getDirectChildElement(
334                 xencEncryptedKey, "ReferenceList", WSConstants.ENC_NS
335             );
336         List<String> dataRefURIs = new LinkedList<String>();
337         if (refList != null) {
338             for (Node node = refList.getFirstChild(); node != null; node = node.getNextSibling()) {
339                 if (Node.ELEMENT_NODE == node.getNodeType()
340                         && WSConstants.ENC_NS.equals(node.getNamespaceURI())
341                         && "DataReference".equals(node.getLocalName())) {
342                     String dataRefURI = ((Element) node).getAttribute("URI");
343                     if (dataRefURI.charAt(0) == '#') {
344                         dataRefURI = dataRefURI.substring(1);
345                     }
346                     dataRefURIs.add(dataRefURI);
347                 }
348             }
349         }
350         return dataRefURIs;
351     }
352     
353     /**
354      * Decrypt all data references
355      */
356     private List<WSDataRef> decryptDataRefs(List<String> dataRefURIs, Document doc,
357         WSDocInfo docInfo, byte[] decryptedBytes, RequestData data
358     ) throws WSSecurityException {
359         //
360         // At this point we have the decrypted session (symmetric) key. According
361         // to W3C XML-Enc this key is used to decrypt _any_ references contained in
362         // the reference list
363         if (dataRefURIs == null || dataRefURIs.isEmpty()) {
364             return null;
365         }
366         List<WSDataRef> dataRefs = new ArrayList<WSDataRef>();
367         for (String dataRefURI : dataRefURIs) {
368             WSDataRef dataRef = 
369                 decryptDataRef(doc, dataRefURI, docInfo, decryptedBytes, data);
370             dataRefs.add(dataRef);
371         }
372         return dataRefs;
373     }
374 
375     /**
376      * Decrypt an EncryptedData element referenced by dataRefURI
377      */
378     private WSDataRef decryptDataRef(
379         Document doc, 
380         String dataRefURI, 
381         WSDocInfo docInfo,
382         byte[] decryptedData,
383         RequestData data
384     ) throws WSSecurityException {
385         if (log.isDebugEnabled()) {
386             log.debug("found data reference: " + dataRefURI);
387         }
388         //
389         // Find the encrypted data element referenced by dataRefURI
390         //
391         Element encryptedDataElement = 
392             ReferenceListProcessor.findEncryptedDataElement(doc, docInfo, dataRefURI);
393         if (encryptedDataElement != null && data.isRequireSignedEncryptedDataElements()) {
394             WSSecurityUtil.verifySignedElement(encryptedDataElement, doc, docInfo.getSecurityHeader());
395         }
396         //
397         // Prepare the SecretKey object to decrypt EncryptedData
398         //
399         String symEncAlgo = X509Util.getEncAlgo(encryptedDataElement);
400         SecretKey symmetricKey = null;
401         try {
402             symmetricKey = WSSecurityUtil.prepareSecretKey(symEncAlgo, decryptedData);
403         } catch (IllegalArgumentException ex) {
404             throw new WSSecurityException(
405                 WSSecurityException.UNSUPPORTED_ALGORITHM, "badEncAlgo", 
406                 new Object[]{symEncAlgo}, ex
407             );
408         }
409         
410         // Check for compliance against the defined AlgorithmSuite
411         AlgorithmSuite algorithmSuite = data.getAlgorithmSuite();
412         if (algorithmSuite != null) {
413             AlgorithmSuiteValidator algorithmSuiteValidator = new
414                 AlgorithmSuiteValidator(algorithmSuite);
415 
416             algorithmSuiteValidator.checkSymmetricKeyLength(symmetricKey.getEncoded().length);
417             algorithmSuiteValidator.checkSymmetricEncryptionAlgorithm(symEncAlgo);
418         }
419 
420         return ReferenceListProcessor.decryptEncryptedData(
421             doc, dataRefURI, encryptedDataElement, symmetricKey, symEncAlgo
422         );
423     }
424     
425     /**
426      * A method to check that the EncryptedKey is compliant with the BSP spec.
427      * @throws WSSecurityException
428      */
429     private void checkBSPCompliance(Element elem, String encAlgo) throws WSSecurityException {
430         String attribute = elem.getAttribute("Type");
431         if (attribute != null && !"".equals(attribute)) {
432             throw new WSSecurityException(
433                 WSSecurityException.FAILED_CHECK, "badAttribute", new Object[]{attribute}
434             );
435         }
436         attribute = elem.getAttribute("MimeType");
437         if (attribute != null && !"".equals(attribute)) {
438             throw new WSSecurityException(
439                 WSSecurityException.FAILED_CHECK, "badAttribute", new Object[]{attribute}
440             );
441         }
442         attribute = elem.getAttribute("Encoding");
443         if (attribute != null && !"".equals(attribute)) {
444             throw new WSSecurityException(
445                 WSSecurityException.FAILED_CHECK, "badAttribute", new Object[]{attribute}
446             );
447         }
448         attribute = elem.getAttribute("Recipient");
449         if (attribute != null && !"".equals(attribute)) {
450             throw new WSSecurityException(
451                 WSSecurityException.FAILED_CHECK, "badAttribute", new Object[]{attribute}
452             );
453         }
454         
455         // EncryptionAlgorithm must be RSA15, or RSAOEP.
456         if (!WSConstants.KEYTRANSPORT_RSA15.equals(encAlgo)
457             && !WSConstants.KEYTRANSPORT_RSAOEP.equals(encAlgo)) {
458             throw new WSSecurityException(
459                 WSSecurityException.INVALID_SECURITY, "badEncAlgo", new Object[]{encAlgo}
460             );
461         }
462     }
463   
464 }