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.message;
21  
22  import org.apache.ws.security.WSConstants;
23  import org.apache.ws.security.WSEncryptionPart;
24  import org.apache.ws.security.WSSConfig;
25  import org.apache.ws.security.WSSecurityException;
26  import org.apache.ws.security.components.crypto.Crypto;
27  import org.apache.ws.security.components.crypto.CryptoType;
28  import org.apache.ws.security.message.token.KerberosSecurity;
29  import org.apache.ws.security.message.token.Reference;
30  import org.apache.ws.security.message.token.SecurityTokenReference;
31  import org.apache.ws.security.util.Base64;
32  import org.apache.ws.security.util.WSSecurityUtil;
33  import org.apache.xml.security.encryption.EncryptedData;
34  import org.apache.xml.security.encryption.XMLCipher;
35  import org.apache.xml.security.encryption.XMLEncryptionException;
36  import org.apache.xml.security.keys.KeyInfo;
37  import org.w3c.dom.Attr;
38  import org.w3c.dom.Document;
39  import org.w3c.dom.Element;
40  import org.w3c.dom.NamedNodeMap;
41  import org.w3c.dom.Node;
42  
43  import javax.crypto.KeyGenerator;
44  import javax.crypto.SecretKey;
45  
46  import java.security.cert.X509Certificate;
47  import java.util.ArrayList;
48  import java.util.List;
49  
50  /**
51   * Encrypts a parts of a message according to WS Specification, X509 profile,
52   * and adds the encryption data.
53   * 
54   * @author Davanum Srinivas (dims@yahoo.com).
55   * @author Werner Dittmann (Werner.Dittmann@apache.org).
56   */
57  public class WSSecEncrypt extends WSSecEncryptedKey {
58      private static org.apache.commons.logging.Log log = 
59          org.apache.commons.logging.LogFactory.getLog(WSSecEncrypt.class);
60      
61      protected byte[] embeddedKey = null;
62  
63      protected String embeddedKeyName = null;
64  
65      /**
66       * SecurityTokenReference to be inserted into EncryptedData/keyInfo element.
67       */
68      protected SecurityTokenReference securityTokenReference = null;
69  
70      /**
71       * Indicates whether to encrypt the symmetric key into an EncryptedKey 
72       * or not.
73       */
74      private boolean encryptSymmKey = true;
75      
76      /**
77       * Custom reference value
78       */
79      private String customReferenceValue;
80  
81      /**
82       * True if the encKeyId is a direct reference to a key identifier instead of a URI to a key
83       */
84      private boolean encKeyIdDirectId;
85      
86      private boolean embedEncryptedKey;
87   
88      public WSSecEncrypt() {
89          super();
90      }
91      
92      public WSSecEncrypt(WSSConfig config) {
93          super(config);
94      }
95      
96      /**
97       * Sets the key to use during embedded encryption.
98       * 
99       * @param key to use during encryption. The key must fit the selected
100      *            symmetrical encryption algorithm
101      */
102     public void setKey(byte[] key) {
103         embeddedKey = key;
104     }
105 
106     /**
107      * Sets the algorithm to encode the symmetric key.
108      * 
109      * Default is the <code>WSConstants.KEYTRANSPORT_RSAOEP</code> algorithm.
110      * 
111      * @param keyEnc specifies the key encoding algorithm.
112      * @see WSConstants#KEYTRANSPORT_RSA15
113      * @see WSConstants#KEYTRANSPORT_RSAOEP
114      */
115     public void setKeyEnc(String keyEnc) {
116         keyEncAlgo = keyEnc;
117     }
118 
119     /**
120      * Set the key name for EMBEDDED_KEYNAME
121      * 
122      * @param embeddedKeyName
123      */
124     public void setEmbeddedKeyName(String embeddedKeyName) {
125         this.embeddedKeyName = embeddedKeyName;
126     }
127     
128     
129     /**
130      * Initialize a WSSec Encrypt.
131      * 
132      * The method prepares and initializes a WSSec Encrypt structure after the
133      * relevant information was set. After preparation of the token references
134      * can be added and encrypted.
135      * 
136      * This method does not add any element to the security header. This must be
137      * done explicitly.
138      * 
139      * @param doc The SOAP envelope as <code>Document</code>
140      * @param crypto An instance of the Crypto API to handle keystore and certificates
141      * @throws WSSecurityException
142      */
143     public void prepare(Document doc, Crypto crypto) throws WSSecurityException {
144         document = doc;
145 
146         //
147         // If no external key (symmetricalKey) was set generate an encryption
148         // key (session key) for this Encrypt element. This key will be
149         // encrypted using the public key of the receiver
150         //
151         if (ephemeralKey == null) {
152             if (symmetricKey == null) {
153                 KeyGenerator keyGen = getKeyGenerator();
154                 symmetricKey = keyGen.generateKey();
155             } 
156             ephemeralKey = symmetricKey.getEncoded();
157         }
158         
159         if (symmetricKey == null) {
160             symmetricKey = WSSecurityUtil.prepareSecretKey(symEncAlgo, ephemeralKey);
161         }
162         
163         //
164         // Get the certificate that contains the public key for the public key
165         // algorithm that will encrypt the generated symmetric (session) key.
166         //
167         if (encryptSymmKey) {
168             X509Certificate remoteCert = useThisCert;
169             if (remoteCert == null) {
170                 CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
171                 cryptoType.setAlias(user);
172                 X509Certificate[] certs = crypto.getX509Certificates(cryptoType);
173                 if (certs == null || certs.length <= 0) {
174                     throw new WSSecurityException(
175                         WSSecurityException.FAILURE,
176                         "noUserCertsFound", 
177                         new Object[] { user, "encryption" }
178                     );
179                 }
180                 remoteCert = certs[0];
181             }
182             prepareInternal(symmetricKey, remoteCert, crypto);
183         } else {
184             encryptedEphemeralKey = ephemeralKey;
185         }
186     }
187     
188 
189     /**
190      * Builds the SOAP envelope with encrypted Body and adds encrypted key.
191      * 
192      * This is a convenience method and for backward compatibility. The method
193      * calls the single function methods in order to perform a <i>one shot
194      * encryption</i>. This method is compatible with the build method of the
195      * previous version with the exception of the additional WSSecHeader
196      * parameter.
197      * 
198      * @param doc the SOAP envelope as <code>Document</code> with plain text Body
199      * @param crypto an instance of the Crypto API to handle keystore and Certificates
200      * @param secHeader the security header element to hold the encrypted key element.
201      * @return the SOAP envelope with encrypted Body as <code>Document</code>
202      * @throws WSSecurityException
203      */
204     public Document build(Document doc, Crypto crypto, WSSecHeader secHeader)
205         throws WSSecurityException {
206         doDebug = log.isDebugEnabled();
207 
208         if (keyIdentifierType == WSConstants.EMBEDDED_KEYNAME
209             || keyIdentifierType == WSConstants.EMBED_SECURITY_TOKEN_REF) {
210             encryptSymmKey = false;
211             document = doc;
212             //
213             // Generate a symmetric key from the specified key (password) for this
214             // algorithm, and set the cipher into encryption mode.
215             //
216             if (symmetricKey == null) {
217                 if (embeddedKey == null) {
218                     throw new WSSecurityException(WSSecurityException.FAILURE, "noKeySupplied");
219                 }
220                 symmetricKey = WSSecurityUtil.prepareSecretKey(symEncAlgo, embeddedKey);
221             }
222         } else {
223             prepare(doc, crypto);
224         }
225         
226         if (envelope == null) {
227             envelope = document.getDocumentElement();
228         }
229         
230         if (parts == null) {
231             parts = new ArrayList<WSEncryptionPart>(1);
232             String soapNamespace = WSSecurityUtil.getSOAPNamespace(envelope);
233             WSEncryptionPart encP = 
234                 new WSEncryptionPart(
235                     WSConstants.ELEM_BODY, 
236                     soapNamespace, 
237                     "Content"
238                 );
239             parts.add(encP);
240         }
241 
242         if (doDebug) {
243             log.debug("Beginning Encryption...");
244         }
245         
246         Element refs = encryptForRef(null, parts);
247         if (encryptedKeyElement != null) {
248             addInternalRefElement(refs);
249             prependToHeader(secHeader); 
250         } else {
251             addExternalRefElement(refs, secHeader);
252         }
253 
254         if (bstToken != null) {
255             prependBSTElementToHeader(secHeader);
256         }
257 
258         log.debug("Encryption complete.");
259         return doc;
260     }
261     
262     /**
263      * Encrypt one or more parts or elements of the message.
264      * 
265      * This method takes a vector of <code>WSEncryptionPart</code> object that
266      * contain information about the elements to encrypt. The method call the
267      * encryption method, takes the reference information generated during
268      * encryption and add this to the <code>xenc:Reference</code> element.
269      * This method can be called after <code>prepare()</code> and can be
270      * called multiple times to encrypt a number of parts or elements.
271      * 
272      * The method generates a <code>xenc:Reference</code> element that <i>must</i>
273      * be added to this token. See <code>addInternalRefElement()</code>.
274      * 
275      * If the <code>dataRef</code> parameter is <code>null</code> the method
276      * creates and initializes a new Reference element.
277      * 
278      * @param dataRef A <code>xenc:Reference</code> element or <code>null</code>
279      * @param references A list containing WSEncryptionPart objects
280      * @return Returns the updated <code>xenc:Reference</code> element
281      * @throws WSSecurityException
282      */
283     public Element encryptForRef(
284         Element dataRef, 
285         List<WSEncryptionPart> references
286     ) throws WSSecurityException {
287 
288         KeyInfo keyInfo = createKeyInfo();
289         List<String> encDataRefs = 
290             doEncryption(
291                 document, getWsConfig(), keyInfo, symmetricKey, symEncAlgo, references, callbackLookup
292             );
293         if (dataRef == null) {
294             dataRef = 
295                 document.createElementNS(
296                     WSConstants.ENC_NS,
297                     WSConstants.ENC_PREFIX + ":ReferenceList"
298                 );
299             //
300             // If we're not placing the ReferenceList in an EncryptedKey structure,
301             // then add the ENC namespace
302             //
303             if (!encryptSymmKey) {
304                 WSSecurityUtil.setNamespace(
305                     dataRef, WSConstants.ENC_NS, WSConstants.ENC_PREFIX
306                 );
307             }
308         }
309         return createDataRefList(document, dataRef, encDataRefs);
310     }
311 
312     /**
313      * @deprecated Use encryptForRef(dataRef, references) instead
314      */
315     public Element encryptForInternalRef(Element dataRef, List<WSEncryptionPart> references)
316         throws WSSecurityException {
317         return encryptForRef(dataRef, references);
318     }
319 
320     /**
321      * @deprecated Use encryptForRef(dataRef, references) instead
322      */
323     public Element encryptForExternalRef(Element dataRef, List<WSEncryptionPart> references)
324         throws WSSecurityException {
325         return encryptForRef(dataRef, references);
326     }
327 
328     /**
329      * Adds the internal Reference element to this Encrypt data.
330      * 
331      * The reference element <i>must</i> be created by the
332      * <code>encryptForInternalRef()</code> method. The reference element is
333      * added to the <code>EncryptedKey</code> element of this encrypt block.
334      * 
335      * @param dataRef The internal <code>enc:Reference</code> element
336      */
337     public void addInternalRefElement(Element dataRef) {
338         encryptedKeyElement.appendChild(dataRef);
339     }
340 
341     /**
342      * Adds (prepends) the external Reference element to the Security header.
343      * 
344      * The reference element <i>must</i> be created by the
345      * <code>encryptForExternalRef() </code> method. The method prepends the
346      * reference element in the SecurityHeader.
347      * 
348      * @param dataRef The external <code>enc:Reference</code> element
349      * @param secHeader The security header.
350      */
351     public void addExternalRefElement(Element dataRef, WSSecHeader secHeader) {
352         WSSecurityUtil.prependChildElement(secHeader.getSecurityHeader(), dataRef);
353     }
354 
355     /**
356      * Perform encryption on the SOAP envelope.
357      * @param doc The document containing the SOAP envelope as document element
358      * @param config The WSSConfig from which to generate wsu:ID's
359      * @param keyInfo The KeyInfo object to set in EncryptedData
360      * @param secretKey The SecretKey object with which to encrypt data
361      * @param encryptionAlgorithm The encryption algorithm URI to use
362      * @param references The list of references to encrypt
363      * @return a List of references to EncryptedData elements
364      * @throws WSSecurityException
365      */
366     public static List<String> doEncryption(
367         Document doc,
368         WSSConfig config,
369         KeyInfo keyInfo,
370         SecretKey secretKey,
371         String encryptionAlgorithm,
372         List<WSEncryptionPart> references,
373         CallbackLookup callbackLookup
374     ) throws WSSecurityException {
375 
376         XMLCipher xmlCipher = null;
377         try {
378             xmlCipher = XMLCipher.getInstance(encryptionAlgorithm);
379         } catch (XMLEncryptionException ex) {
380             throw new WSSecurityException(
381                 WSSecurityException.UNSUPPORTED_ALGORITHM, null, null, ex
382             );
383         }
384 
385         List<String> encDataRef = new ArrayList<String>();
386         for (int part = 0; part < references.size(); part++) {
387             WSEncryptionPart encPart = references.get(part);
388             //
389             // Get the data to encrypt.
390             //
391             if (callbackLookup == null) {
392                 callbackLookup = new DOMCallbackLookup(doc);
393             }
394             List<Element> elementsToEncrypt = 
395                 WSSecurityUtil.findElements(encPart, callbackLookup, doc);
396             if (elementsToEncrypt == null || elementsToEncrypt.size() == 0) {
397                 throw new WSSecurityException(
398                     WSSecurityException.FAILURE,
399                     "noEncElement", 
400                     new Object[] {"{" + encPart.getNamespace() + "}" + encPart.getName()}
401                 );
402             }
403 
404             String modifier = encPart.getEncModifier();
405             for (Element elementToEncrypt : elementsToEncrypt) {
406                 String id = 
407                     encryptElement(doc, elementToEncrypt, modifier, config, xmlCipher, 
408                                    secretKey, keyInfo);
409                 encPart.setEncId(id);
410                 encDataRef.add("#" + id);
411             }
412                 
413             if (part != (references.size() - 1)) {
414                 try {
415                     keyInfo = new KeyInfo((Element) keyInfo.getElement().cloneNode(true), null);
416                 } catch (Exception ex) {
417                     throw new WSSecurityException(
418                         WSSecurityException.FAILED_ENCRYPTION, null, null, ex
419                     );
420                 }
421             }
422         }
423         return encDataRef;
424     }
425     
426     /**
427      * Encrypt an element.
428      */
429     private static String encryptElement(
430         Document doc,
431         Element elementToEncrypt,
432         String modifier,
433         WSSConfig config,
434         XMLCipher xmlCipher,
435         SecretKey secretKey,
436         KeyInfo keyInfo
437     ) throws WSSecurityException {
438 
439         boolean content = "Content".equals(modifier) ? true : false;
440         //
441         // Encrypt data, and set necessary attributes in xenc:EncryptedData
442         //
443         String xencEncryptedDataId = 
444             config.getIdAllocator().createId("ED-", elementToEncrypt);
445         try {
446             String headerId = "";
447             if ("Header".equals(modifier)) {
448                 Element elem = 
449                     doc.createElementNS(
450                         WSConstants.WSSE11_NS, "wsse11:" + WSConstants.ENCRYPTED_HEADER
451                     );
452                 WSSecurityUtil.setNamespace(elem, WSConstants.WSSE11_NS, WSConstants.WSSE11_PREFIX);
453                 String wsuPrefix = 
454                     WSSecurityUtil.setNamespace(elem, WSConstants.WSU_NS, WSConstants.WSU_PREFIX);
455                 headerId = config.getIdAllocator().createId("EH-", elementToEncrypt);
456                 elem.setAttributeNS(
457                     WSConstants.WSU_NS, wsuPrefix + ":Id", headerId
458                 );
459                 //
460                 // Add the EncryptedHeader node to the element to be encrypted's parent
461                 // (i.e. the SOAP header). Add the element to be encrypted to the Encrypted
462                 // Header node as well
463                 //
464                 Node parent = elementToEncrypt.getParentNode();
465                 elementToEncrypt = (Element)parent.replaceChild(elem, elementToEncrypt);
466                 elem.appendChild(elementToEncrypt);
467                 
468                 NamedNodeMap map = elementToEncrypt.getAttributes();
469                 for (int i = 0; i < map.getLength(); i++) {
470                     Attr attr = (Attr)map.item(i);
471                     if (attr.getNamespaceURI().equals(WSConstants.URI_SOAP11_ENV)
472                         || attr.getNamespaceURI().equals(WSConstants.URI_SOAP12_ENV)) {                         
473                         String soapEnvPrefix = 
474                             WSSecurityUtil.setNamespace(
475                                 elem, attr.getNamespaceURI(), WSConstants.DEFAULT_SOAP_PREFIX
476                             );
477                         elem.setAttributeNS(
478                             attr.getNamespaceURI(), 
479                             soapEnvPrefix + ":" + attr.getLocalName(), 
480                             attr.getValue()
481                         );
482                     }
483                 }
484             }
485             
486             xmlCipher.init(XMLCipher.ENCRYPT_MODE, secretKey);
487             EncryptedData encData = xmlCipher.getEncryptedData();
488             encData.setId(xencEncryptedDataId);
489             encData.setKeyInfo(keyInfo);
490             xmlCipher.doFinal(doc, elementToEncrypt, content);
491             return xencEncryptedDataId;
492         } catch (Exception ex) {
493             throw new WSSecurityException(
494                 WSSecurityException.FAILED_ENCRYPTION, null, null, ex
495             );
496         }
497     }
498     
499     /**
500      * Create a KeyInfo object
501      */
502     private KeyInfo createKeyInfo() throws WSSecurityException {
503 
504         KeyInfo keyInfo = new KeyInfo(document);
505         if (embedEncryptedKey) {
506             keyInfo.addUnknownElement(getEncryptedKeyElement());
507         } else if (keyIdentifierType == WSConstants.ENCRYPTED_KEY_SHA1_IDENTIFIER) {
508             SecurityTokenReference secToken = new SecurityTokenReference(document);
509             secToken.addWSSENamespace();
510             if (customReferenceValue != null) {
511                 secToken.setKeyIdentifierEncKeySHA1(customReferenceValue);
512             } else {
513                 byte[] encodedBytes = WSSecurityUtil.generateDigest(encryptedEphemeralKey);
514                 secToken.setKeyIdentifierEncKeySHA1(Base64.encode(encodedBytes));
515             }
516             secToken.addTokenType(WSConstants.WSS_ENC_KEY_VALUE_TYPE);
517             keyInfo.addUnknownElement(secToken.getElement());
518         } else if (keyIdentifierType == WSConstants.EMBEDDED_KEYNAME) {
519             keyInfo.addKeyName(embeddedKeyName == null ? user : embeddedKeyName);
520         } else if (WSConstants.WSS_SAML_KI_VALUE_TYPE.equals(customReferenceValue)) {
521             SecurityTokenReference secToken = new SecurityTokenReference(document);
522             secToken.addWSSENamespace();
523             secToken.addTokenType(WSConstants.WSS_SAML_TOKEN_TYPE);
524             secToken.setKeyIdentifier(WSConstants.WSS_SAML_KI_VALUE_TYPE, encKeyId);
525             keyInfo.addUnknownElement(secToken.getElement());
526         } else if (WSConstants.WSS_SAML2_KI_VALUE_TYPE.equals(customReferenceValue)) {
527             SecurityTokenReference secToken = new SecurityTokenReference(document);
528             secToken.addWSSENamespace();
529             secToken.addTokenType(WSConstants.WSS_SAML2_TOKEN_TYPE);
530             secToken.setKeyIdentifier(WSConstants.WSS_SAML2_KI_VALUE_TYPE, encKeyId);
531             keyInfo.addUnknownElement(secToken.getElement());
532         } else if (WSConstants.WSS_KRB_KI_VALUE_TYPE.equals(customReferenceValue)) {
533             SecurityTokenReference secToken = new SecurityTokenReference(document);
534             secToken.addWSSENamespace();
535             secToken.addTokenType(WSConstants.WSS_GSS_KRB_V5_AP_REQ);
536             secToken.setKeyIdentifier(customReferenceValue, encKeyId, true);
537             keyInfo.addUnknownElement(secToken.getElement());
538         } else if (securityTokenReference != null) {
539             Element tmpE = securityTokenReference.getElement();
540             tmpE.setAttributeNS(
541                 WSConstants.XMLNS_NS, "xmlns:" + tmpE.getPrefix(), tmpE.getNamespaceURI()
542             );
543             keyInfo.addUnknownElement(securityTokenReference.getElement());
544         } else {
545             SecurityTokenReference secToken = new SecurityTokenReference(document);
546             secToken.addWSSENamespace();
547             Reference ref = new Reference(document);
548             if (encKeyIdDirectId) {
549                 ref.setURI(encKeyId);
550             } else {
551                 ref.setURI("#" + encKeyId);                    
552             }
553             if (customReferenceValue != null) {
554                 ref.setValueType(customReferenceValue);
555             }
556             secToken.setReference(ref);
557             if (KerberosSecurity.isKerberosToken(customReferenceValue)) {
558                 secToken.addTokenType(customReferenceValue);
559             } else if (!WSConstants.WSS_USERNAME_TOKEN_VALUE_TYPE.equals(customReferenceValue)) {
560                 secToken.addTokenType(WSConstants.WSS_ENC_KEY_VALUE_TYPE);
561             }
562             keyInfo.addUnknownElement(secToken.getElement());
563         }
564         Element keyInfoElement = keyInfo.getElement();
565         keyInfoElement.setAttributeNS(
566             WSConstants.XMLNS_NS, "xmlns:" + WSConstants.SIG_PREFIX, WSConstants.SIG_NS
567         );
568         
569         return keyInfo;
570     }
571 
572     /**
573      * Create DOM subtree for <code>xenc:EncryptedKey</code>
574      * 
575      * @param doc the SOAP envelope parent document
576      * @param referenceList
577      * @param encDataRefs
578      * @return an <code>xenc:EncryptedKey</code> element
579      */
580     public static Element createDataRefList(
581         Document doc,
582         Element referenceList, 
583         List<String> encDataRefs
584     ) {
585         for (String dataReferenceUri : encDataRefs) {
586             Element dataReference = 
587                 doc.createElementNS(
588                     WSConstants.ENC_NS, WSConstants.ENC_PREFIX + ":DataReference"
589                 );
590             dataReference.setAttributeNS(null, "URI", dataReferenceUri);
591             referenceList.appendChild(dataReference);
592         }
593         return referenceList;
594     }
595 
596     /**
597      * @return Return the SecurityTokenRefernce
598      */
599     public SecurityTokenReference getSecurityTokenReference() {
600         return securityTokenReference;
601     }
602 
603     /**
604      * @param reference
605      */
606     public void setSecurityTokenReference(SecurityTokenReference reference) {
607         securityTokenReference = reference;
608     }
609 
610     public boolean isEncryptSymmKey() {
611         return encryptSymmKey;
612     }
613     
614     public void setEncryptSymmKey(boolean encryptSymmKey) {
615         this.encryptSymmKey = encryptSymmKey;
616     }
617     
618     public void setCustomReferenceValue(String customReferenceValue) {
619         this.customReferenceValue = customReferenceValue;
620     }
621     
622     public void setEncKeyIdDirectId(boolean b) {
623         encKeyIdDirectId = b;
624     }
625     
626     public void setEmbedEncryptedKey(boolean embedEncryptedKey) {
627         this.embedEncryptedKey = embedEncryptedKey;
628     }
629     
630     public boolean isEmbedEncryptedKey() {
631         return embedEncryptedKey;
632     }
633 }