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.wss4j.dom.message;
21  
22  import java.security.Provider;
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import javax.crypto.SecretKey;
27  import javax.crypto.spec.SecretKeySpec;
28  import javax.security.auth.DestroyFailedException;
29  
30  import org.apache.wss4j.common.WSEncryptionPart;
31  import org.apache.wss4j.common.crypto.Crypto;
32  import org.apache.wss4j.common.ext.WSSecurityException;
33  import org.apache.wss4j.common.token.Reference;
34  import org.apache.wss4j.common.token.SecurityTokenReference;
35  import org.apache.wss4j.common.util.FIPSUtils;
36  import org.apache.wss4j.common.util.XMLUtils;
37  import org.apache.wss4j.dom.WSConstants;
38  import org.apache.wss4j.dom.message.token.KerberosSecurity;
39  import org.apache.wss4j.dom.util.WSSecurityUtil;
40  import org.apache.xml.security.algorithms.JCEMapper;
41  import org.apache.xml.security.encryption.Serializer;
42  import org.apache.xml.security.keys.KeyInfo;
43  import org.w3c.dom.Document;
44  import org.w3c.dom.Element;
45  
46  /**
47   * Encrypts a parts of a message according to WS Specification, X509 profile,
48   * and adds the encryption data.
49   */
50  public class WSSecEncrypt extends WSSecEncryptedKey {
51      private static final org.slf4j.Logger LOG =
52          org.slf4j.LoggerFactory.getLogger(WSSecEncrypt.class);
53  
54      /**
55       * SecurityTokenReference to be inserted into EncryptedData/keyInfo element.
56       */
57      private SecurityTokenReference securityTokenReference;
58  
59      /**
60       * Indicates whether to encrypt the symmetric key into an EncryptedKey
61       * or not.
62       */
63      private boolean encryptSymmKey = true;
64  
65      /**
66       * Custom reference value
67       */
68      private String customReferenceValue;
69  
70      /**
71       * True if the encKeyId is a direct reference to a key identifier instead of a URI to a key
72       */
73      private boolean encKeyIdDirectId;
74  
75      private boolean embedEncryptedKey;
76  
77      private List<Element> attachmentEncryptedDataElements;
78  
79      private Serializer encryptionSerializer;
80  
81      /**
82       * Algorithm to be used with the ephemeral key
83       */
84      private String symEncAlgo = FIPSUtils.isFIPSEnabled()
85          ? WSConstants.AES_128_GCM : WSConstants.AES_128;
86  
87      public WSSecEncrypt(WSSecHeader securityHeader) {
88          super(securityHeader);
89      }
90  
91      public WSSecEncrypt(Document doc) {
92          super(doc);
93      }
94  
95      public WSSecEncrypt(Document doc, Provider provider) {
96          super(doc, provider);
97      }
98  
99      /**
100      * Initialize a WSSec Encrypt.
101      *
102      * The method prepares and initializes a WSSec Encrypt structure after the
103      * relevant information was set. After preparation of the token references
104      * can be added and encrypted.
105      *
106      * This method does not add any element to the security header. This must be
107      * done explicitly.
108      *
109      * @param crypto An instance of the Crypto API to handle keystore and certificates
110      * @param symmetricKey The symmetric key to use for encryption
111      * @throws WSSecurityException
112      */
113     public void prepare(Crypto crypto, SecretKey symmetricKey) throws WSSecurityException {
114         attachmentEncryptedDataElements = new ArrayList<>();
115 
116         if (encryptSymmKey) {
117             super.prepare(crypto, symmetricKey);
118         } else {
119             setEncryptedKeySHA1(symmetricKey.getEncoded());
120         }
121     }
122 
123 
124     /**
125      * Builds the SOAP envelope with encrypted Body and adds encrypted key.
126      *
127      * This is a convenience method and for backward compatibility. The method
128      * calls the single function methods in order to perform a <i>one shot
129      * encryption</i>.
130      *
131      * @param crypto an instance of the Crypto API to handle keystore and Certificates
132      * @param symmetricKey The symmetric key to use for encryption
133      * @return the SOAP envelope with encrypted Body as <code>Document</code>
134      * @throws WSSecurityException
135      */
136     public Document build(Crypto crypto, SecretKey symmetricKey)
137         throws WSSecurityException {
138 
139         prepare(crypto, symmetricKey);
140 
141         LOG.debug("Beginning Encryption...");
142 
143         Element refs = encrypt(symmetricKey);
144 
145         addAttachmentEncryptedDataElements();
146         if (getEncryptedKeyElement() != null) {
147             addInternalRefElement(refs);
148             prependToHeader();
149         } else {
150             addExternalRefElement(refs);
151         }
152 
153         prependBSTElementToHeader();
154 
155         LOG.debug("Encryption complete.");
156         return getDocument();
157     }
158 
159     /**
160      * Perform encryption using the given symmetric key
161      * @param symmetricKey The symmetric key to use for encryption
162      * @return the EncryptedData element
163      * @throws WSSecurityException
164      */
165     public Element encrypt(SecretKey symmetricKey) throws WSSecurityException {
166         if (getParts().isEmpty()) {
167             getParts().add(WSSecurityUtil.getDefaultEncryptionPart(getDocument()));
168         }
169 
170         return encryptForRef(null, getParts(), symmetricKey);
171     }
172 
173     /**
174      * Encrypt one or more parts or elements of the message.
175      *
176      * This method takes a list of <code>WSEncryptionPart</code> object that
177      * contain information about the elements to encrypt. The method call the
178      * encryption method, takes the reference information generated during
179      * encryption and add this to the <code>xenc:Reference</code> element.
180      * This method can be called after <code>prepare()</code> and can be
181      * called multiple times to encrypt a number of parts or elements.
182      *
183      * The method generates a <code>xenc:Reference</code> element that <i>must</i>
184      * be added to this token. See <code>addInternalRefElement()</code>.
185      *
186      * If the <code>dataRef</code> parameter is <code>null</code> the method
187      * creates and initializes a new Reference element.
188      *
189      * @param dataRef A <code>xenc:Reference</code> element or <code>null</code>
190      * @param references A list containing WSEncryptionPart objects
191      * @param symmetricKey The symmetric key to use for encryption
192      * @return Returns the updated <code>xenc:Reference</code> element
193      * @throws WSSecurityException
194      */
195     public Element encryptForRef(
196         Element dataRef,
197         List<WSEncryptionPart> references,
198         SecretKey symmetricKey
199     ) throws WSSecurityException {
200         KeyInfo keyInfo = createKeyInfo();
201         //the sun/oracle jce provider doesn't like a foreign SecretKey impl.
202         //this occurs e.g. with a kerberos session-key. It doesn't matter for the bouncy-castle provider
203         //so create a new secretKeySpec to make everybody happy.
204         String keyAlgorithm = JCEMapper.getJCEKeyAlgorithmFromURI(getSymmetricEncAlgorithm());
205         SecretKeySpec secretKeySpec = new SecretKeySpec(symmetricKey.getEncoded(), keyAlgorithm);
206 
207         Encryptor encryptor = new Encryptor();
208         encryptor.setDoc(getDocument());
209         encryptor.setSecurityHeader(getSecurityHeader());
210         encryptor.setIdAllocator(getIdAllocator());
211         encryptor.setCallbackLookup(callbackLookup);
212         encryptor.setAttachmentCallbackHandler(attachmentCallbackHandler);
213         encryptor.setStoreBytesInAttachment(storeBytesInAttachment);
214         encryptor.setEncryptionSerializer(getEncryptionSerializer());
215         encryptor.setExpandXopInclude(isExpandXopInclude());
216         encryptor.setWsDocInfo(getWsDocInfo());
217         List<String> encDataRefs =
218             encryptor.doEncryption(keyInfo, secretKeySpec, getSymmetricEncAlgorithm(), references, attachmentEncryptedDataElements);
219 
220         // Clean the secret key from memory now that we're done with it
221         try {
222             secretKeySpec.destroy();
223         } catch (DestroyFailedException e) {
224             LOG.debug("Error destroying key: {}", e.getMessage());
225         }
226 
227         if (encDataRefs.isEmpty()) {
228             return null;
229         }
230 
231         if (dataRef == null) {
232             dataRef =
233                 getDocument().createElementNS(
234                     WSConstants.ENC_NS,
235                     WSConstants.ENC_PREFIX + ":ReferenceList"
236                 );
237             //
238             // If we're not placing the ReferenceList in an EncryptedKey structure,
239             // then add the ENC namespace
240             //
241             if (!encryptSymmKey) {
242                 XMLUtils.setNamespace(
243                     dataRef, WSConstants.ENC_NS, WSConstants.ENC_PREFIX
244                 );
245             }
246         }
247         return createDataRefList(getDocument(), dataRef, encDataRefs);
248     }
249 
250     /**
251      * Adds the internal Reference element to this Encrypt data.
252      *
253      * The reference element <i>must</i> be created by the
254      * <code>encryptForInternalRef()</code> method. The reference element is
255      * added to the <code>EncryptedKey</code> element of this encrypt block.
256      *
257      * @param dataRef The internal <code>enc:Reference</code> element
258      */
259     public void addInternalRefElement(Element dataRef) {
260         if (dataRef != null) {
261             getEncryptedKeyElement().appendChild(dataRef);
262         }
263     }
264 
265     /**
266      * Adds (prepends) the external Reference element to the Security header.
267      *
268      * The reference element <i>must</i> be created by the
269      * <code>encryptForExternalRef() </code> method. The method prepends the
270      * reference element in the SecurityHeader.
271      *
272      * @param dataRef The external <code>enc:Reference</code> element
273      */
274     public void addExternalRefElement(Element dataRef) {
275         if (dataRef != null) {
276             Element secHeaderElement = getSecurityHeader().getSecurityHeaderElement();
277             WSSecurityUtil.prependChildElement(secHeaderElement, dataRef);
278         }
279     }
280 
281     public void addAttachmentEncryptedDataElements() {
282         if (attachmentEncryptedDataElements != null) {
283             for (Element encryptedData : attachmentEncryptedDataElements) {
284                 Element secHeaderElement = getSecurityHeader().getSecurityHeaderElement();
285                 WSSecurityUtil.prependChildElement(secHeaderElement, encryptedData);
286             }
287         }
288     }
289 
290     /**
291      * Create a KeyInfo object
292      */
293     private KeyInfo createKeyInfo() throws WSSecurityException {
294 
295         KeyInfo keyInfo = new KeyInfo(getDocument());
296         if (embedEncryptedKey) {
297             keyInfo.addUnknownElement(getEncryptedKeyElement());
298         } else if (keyIdentifierType == WSConstants.ENCRYPTED_KEY_SHA1_IDENTIFIER) {
299             SecurityTokenReference secToken = new SecurityTokenReference(getDocument());
300             if (addWSUNamespace) {
301                 secToken.addWSUNamespace();
302             }
303             secToken.addWSSENamespace();
304             if (customReferenceValue != null) {
305                 secToken.setKeyIdentifierEncKeySHA1(customReferenceValue);
306             } else {
307                 secToken.setKeyIdentifierEncKeySHA1(getEncryptedKeySHA1());
308             }
309             secToken.addTokenType(WSConstants.WSS_ENC_KEY_VALUE_TYPE);
310             keyInfo.addUnknownElement(secToken.getElement());
311         } else if (WSConstants.WSS_SAML_KI_VALUE_TYPE.equals(customReferenceValue)) {
312             SecurityTokenReference secToken = new SecurityTokenReference(getDocument());
313             if (addWSUNamespace) {
314                 secToken.addWSUNamespace();
315             }
316             secToken.addWSSENamespace();
317             secToken.addTokenType(WSConstants.WSS_SAML_TOKEN_TYPE);
318             secToken.setKeyIdentifier(WSConstants.WSS_SAML_KI_VALUE_TYPE, getId());
319             keyInfo.addUnknownElement(secToken.getElement());
320         } else if (WSConstants.WSS_SAML2_KI_VALUE_TYPE.equals(customReferenceValue)) {
321             SecurityTokenReference secToken = new SecurityTokenReference(getDocument());
322             if (addWSUNamespace) {
323                 secToken.addWSUNamespace();
324             }
325             secToken.addWSSENamespace();
326             secToken.addTokenType(WSConstants.WSS_SAML2_TOKEN_TYPE);
327             secToken.setKeyIdentifier(WSConstants.WSS_SAML2_KI_VALUE_TYPE, getId());
328             keyInfo.addUnknownElement(secToken.getElement());
329         } else if (WSConstants.WSS_KRB_KI_VALUE_TYPE.equals(customReferenceValue)) {
330             SecurityTokenReference secToken = new SecurityTokenReference(getDocument());
331             if (addWSUNamespace) {
332                 secToken.addWSUNamespace();
333             }
334             secToken.addWSSENamespace();
335             secToken.addTokenType(WSConstants.WSS_GSS_KRB_V5_AP_REQ);
336             secToken.setKeyIdentifier(customReferenceValue, getId(), true);
337             keyInfo.addUnknownElement(secToken.getElement());
338         } else if (securityTokenReference != null) {
339             Element tmpE = securityTokenReference.getElement();
340             tmpE.setAttributeNS(
341                 WSConstants.XMLNS_NS, "xmlns:" + tmpE.getPrefix(), tmpE.getNamespaceURI()
342             );
343             keyInfo.addUnknownElement(securityTokenReference.getElement());
344         } else if (getId() != null) {
345             SecurityTokenReference secToken = new SecurityTokenReference(getDocument());
346             if (addWSUNamespace) {
347                 secToken.addWSUNamespace();
348             }
349             secToken.addWSSENamespace();
350             Reference ref = new Reference(getDocument());
351             if (encKeyIdDirectId) {
352                 ref.setURI(getId());
353             } else {
354                 ref.setURI("#" + getId());
355             }
356             if (customReferenceValue != null) {
357                 ref.setValueType(customReferenceValue);
358             }
359             secToken.setReference(ref);
360             if (KerberosSecurity.isKerberosToken(customReferenceValue)) {
361                 secToken.addTokenType(customReferenceValue);
362             } else if (!WSConstants.WSS_USERNAME_TOKEN_VALUE_TYPE.equals(customReferenceValue)) {
363                 secToken.addTokenType(WSConstants.WSS_ENC_KEY_VALUE_TYPE);
364             }
365             keyInfo.addUnknownElement(secToken.getElement());
366         } else if (!encryptSymmKey && (keyIdentifierType == WSConstants.ISSUER_SERIAL
367                 || keyIdentifierType == WSConstants.ISSUER_SERIAL_QUOTE_FORMAT)) {
368             SecurityTokenReference secToken = new SecurityTokenReference(getDocument());
369             if (addWSUNamespace) {
370                 secToken.addWSUNamespace();
371             }
372             secToken.addWSSENamespace();
373             if (customReferenceValue != null) {
374                 secToken.setKeyIdentifierEncKeySHA1(customReferenceValue);
375             } else {
376                 secToken.setKeyIdentifierEncKeySHA1(getEncryptedKeySHA1());
377             }
378             secToken.addTokenType(WSConstants.WSS_ENC_KEY_VALUE_TYPE);
379             keyInfo.addUnknownElement(secToken.getElement());
380         }
381 
382         Element keyInfoElement = keyInfo.getElement();
383         keyInfoElement.setAttributeNS(
384             WSConstants.XMLNS_NS, "xmlns:" + WSConstants.SIG_PREFIX, WSConstants.SIG_NS
385         );
386 
387         return keyInfo;
388     }
389 
390     /**
391      * Create DOM subtree for <code>xenc:EncryptedKey</code>
392      *
393      * @param doc the SOAP envelope parent document
394      * @param referenceList
395      * @param encDataRefs
396      * @return an <code>xenc:EncryptedKey</code> element
397      */
398     public static Element createDataRefList(
399         Document doc,
400         Element referenceList,
401         List<String> encDataRefs
402     ) {
403         for (String dataReferenceUri : encDataRefs) {
404             Element dataReference =
405                 doc.createElementNS(
406                     WSConstants.ENC_NS, WSConstants.ENC_PREFIX + ":DataReference"
407                 );
408             dataReference.setAttributeNS(null, "URI", dataReferenceUri);
409             referenceList.appendChild(dataReference);
410         }
411         return referenceList;
412     }
413 
414     /**
415      * @return Return the SecurityTokenRefernce
416      */
417     public SecurityTokenReference getSecurityTokenReference() {
418         return securityTokenReference;
419     }
420 
421     /**
422      * @param reference
423      */
424     public void setSecurityTokenReference(SecurityTokenReference reference) {
425         securityTokenReference = reference;
426     }
427 
428     public boolean isEncryptSymmKey() {
429         return encryptSymmKey;
430     }
431 
432     public void setEncryptSymmKey(boolean encryptSymmKey) {
433         this.encryptSymmKey = encryptSymmKey;
434     }
435 
436     public void setCustomReferenceValue(String customReferenceValue) {
437         this.customReferenceValue = customReferenceValue;
438     }
439 
440     public void setEncKeyIdDirectId(boolean b) {
441         encKeyIdDirectId = b;
442     }
443 
444     public void setEmbedEncryptedKey(boolean embedEncryptedKey) {
445         this.embedEncryptedKey = embedEncryptedKey;
446     }
447 
448     public boolean isEmbedEncryptedKey() {
449         return embedEncryptedKey;
450     }
451 
452     public List<Element> getAttachmentEncryptedDataElements() {
453         return attachmentEncryptedDataElements;
454     }
455 
456     public Serializer getEncryptionSerializer() {
457         return encryptionSerializer;
458     }
459 
460     public void setEncryptionSerializer(Serializer encryptionSerializer) {
461         this.encryptionSerializer = encryptionSerializer;
462     }
463 
464     /**
465      * Set the name of the symmetric encryption algorithm to use.
466      *
467      * This encryption algorithm is used to encrypt the data. If the algorithm
468      * is not set then AES128 is used. Refer to WSConstants which algorithms are
469      * supported.
470      *
471      * @param algo Is the name of the encryption algorithm
472      * @see WSConstants#TRIPLE_DES
473      * @see WSConstants#AES_128
474      * @see WSConstants#AES_192
475      * @see WSConstants#AES_256
476      */
477     public void setSymmetricEncAlgorithm(String algo) {
478         symEncAlgo = algo;
479     }
480 
481 
482     /**
483      * Get the name of symmetric encryption algorithm to use.
484      *
485      * The name of the encryption algorithm to encrypt the data, i.e. the SOAP
486      * Body. Refer to WSConstants which algorithms are supported.
487      *
488      * @return the name of the currently selected symmetric encryption algorithm
489      * @see WSConstants#TRIPLE_DES
490      * @see WSConstants#AES_128
491      * @see WSConstants#AES_192
492      * @see WSConstants#AES_256
493      */
494     public String getSymmetricEncAlgorithm() {
495         return symEncAlgo;
496     }
497 
498 }