View Javadoc

1   /*
2    * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.opensaml.saml2.encryption;
18  
19  import java.security.Key;
20  import java.security.NoSuchAlgorithmException;
21  import java.util.ArrayList;
22  import java.util.List;
23  
24  import javax.xml.namespace.QName;
25  
26  import org.opensaml.Configuration;
27  import org.opensaml.common.IdentifierGenerator;
28  import org.opensaml.common.impl.SecureRandomIdentifierGenerator;
29  import org.opensaml.saml2.core.Assertion;
30  import org.opensaml.saml2.core.Attribute;
31  import org.opensaml.saml2.core.BaseID;
32  import org.opensaml.saml2.core.EncryptedAssertion;
33  import org.opensaml.saml2.core.EncryptedAttribute;
34  import org.opensaml.saml2.core.EncryptedElementType;
35  import org.opensaml.saml2.core.EncryptedID;
36  import org.opensaml.saml2.core.NameID;
37  import org.opensaml.saml2.core.NewEncryptedID;
38  import org.opensaml.saml2.core.NewID;
39  import org.opensaml.xml.XMLObject;
40  import org.opensaml.xml.XMLObjectBuilderFactory;
41  import org.opensaml.xml.encryption.CarriedKeyName;
42  import org.opensaml.xml.encryption.DataReference;
43  import org.opensaml.xml.encryption.EncryptedData;
44  import org.opensaml.xml.encryption.EncryptedKey;
45  import org.opensaml.xml.encryption.EncryptionConstants;
46  import org.opensaml.xml.encryption.EncryptionException;
47  import org.opensaml.xml.encryption.EncryptionParameters;
48  import org.opensaml.xml.encryption.KeyEncryptionParameters;
49  import org.opensaml.xml.encryption.ReferenceList;
50  import org.opensaml.xml.encryption.XMLEncryptionBuilder;
51  import org.opensaml.xml.security.SecurityException;
52  import org.opensaml.xml.security.SecurityHelper;
53  import org.opensaml.xml.security.keyinfo.KeyInfoGenerator;
54  import org.opensaml.xml.signature.KeyInfo;
55  import org.opensaml.xml.signature.KeyName;
56  import org.opensaml.xml.signature.RetrievalMethod;
57  import org.opensaml.xml.signature.XMLSignatureBuilder;
58  import org.opensaml.xml.util.DatatypeHelper;
59  import org.slf4j.Logger;
60  import org.slf4j.LoggerFactory;
61  import org.w3c.dom.Document;
62  
63  /**
64   * Encrypter for SAML 2 SAMLObjects which has specific options for generating instances of subtypes of
65   * {@link EncryptedElementType}.
66   * 
67   * <p>
68   * Overload methods are provided for encrypting various SAML 2 elements to their corresponding
69   * encrypted element variant of {@link EncryptedElementType}.
70   * </p>
71   * 
72   * <p>
73   * Support is also provided for differing placement options for any associated EncryptedKeys that may
74   * be generated. The options are:
75   * <ul>
76   *   <li><code>INLINE</code>: EncryptedKeys will placed inside the KeyInfo element of the EncryptedData element</li>
77   *   <li><code>PEER</code>: EncryptedKeys will be placed as peer elements of the EncryptedData inside the 
78   *         EncryptedElementType element</li>
79   * </ul>
80   * The default placement is <code>INLINE</code>.
81   * </p>
82   * 
83   * <p>
84   * The EncryptedKey forward and back referencing behavior associated with these key placement options
85   * is intended to be consistent with the guidelines detailed in SAML 2 Errata E43.  See that document
86   * for further information.
87   * </p>
88   * 
89   * <p>
90   * For information on other parameters and options, and general XML Encryption issues,
91   * see {@link org.opensaml.xml.encryption.Encrypter}.
92   * </p>
93   * 
94   */
95  public class Encrypter extends org.opensaml.xml.encryption.Encrypter {
96  
97      /**
98       * Options for where to place the resulting EncryptedKey elements with respect
99       * to the associated EncryptedData element.
100      */
101     public enum KeyPlacement {
102         /** Place the EncryptedKey element(s) as a peer to the EncryptedData inside the EncryptedElementType. */
103         PEER,
104     
105         /** Place the EncryptedKey element(s) within the KeyInfo of the EncryptedData. */
106         INLINE
107     }
108     
109     /** Factory for building XMLObject instances. */
110     private XMLObjectBuilderFactory builderFactory;
111     
112     /** Builder for KeyInfo objects. */
113     private XMLSignatureBuilder<KeyInfo> keyInfoBuilder;
114     
115     /** Builder for DataReference objects. */
116     private XMLEncryptionBuilder<DataReference> dataReferenceBuilder;
117     
118     /** Builder for ReferenceList objects. */
119     private XMLEncryptionBuilder<ReferenceList> referenceListBuilder;
120     
121     /** Builder for RetrievalMethod objects. */
122     private XMLSignatureBuilder<RetrievalMethod> retrievalMethodBuilder;
123     
124     /** Builder for KeyName objects. */
125     private XMLSignatureBuilder<KeyName> keyNameBuilder;
126     
127     /** Builder for CarriedKeyName objects. */
128     private XMLEncryptionBuilder<CarriedKeyName> carriedKeyNameBuilder;
129     
130     /** Generator for XML ID attribute values. */
131     private IdentifierGenerator idGenerator;
132     
133     /** The parameters to use for encrypting the data. */
134     private EncryptionParameters encParams;
135     
136     /** The parameters to use for encrypting (wrapping) the data encryption key. */
137     private List<KeyEncryptionParameters> kekParamsList;
138     
139     /** The option for where to place the generated EncryptedKey elements. */
140     private KeyPlacement keyPlacement;
141 
142     /** Class logger. */
143     private final Logger log = LoggerFactory.getLogger(Encrypter.class);
144 
145     
146     /**
147      * Constructor.
148      *
149      * @param dataEncParams the data encryption parameters
150      * @param keyEncParams the key encryption parameters
151      */
152     public Encrypter(EncryptionParameters dataEncParams, List<KeyEncryptionParameters> keyEncParams) {
153         super();
154         
155         this.encParams = dataEncParams;
156         this.kekParamsList = keyEncParams;
157         
158         init();
159     }
160  
161     /**
162      * Constructor.
163      *
164      * @param dataEncParams the data encryption parameters
165      * @param keyEncParam the key encryption parameter
166      */
167     public Encrypter(EncryptionParameters dataEncParams, KeyEncryptionParameters keyEncParam) {
168         super();
169         
170         List<KeyEncryptionParameters> keks = new ArrayList<KeyEncryptionParameters>();
171         keks.add(keyEncParam);
172         
173         this.encParams = dataEncParams;
174         this.kekParamsList = keks;
175         
176         init();
177     }
178     
179     /**
180      * Constructor.
181      *
182      * @param dataEncParams the data encryption parameters
183      */
184     public Encrypter(EncryptionParameters dataEncParams) {
185         super();
186         
187         List<KeyEncryptionParameters> keks = new ArrayList<KeyEncryptionParameters>();
188         
189         this.encParams = dataEncParams;
190         this.kekParamsList = keks;
191         
192         init();
193     }
194     
195     /**
196      * Helper method for constructors.
197      */
198     private void init() {
199         builderFactory = Configuration.getBuilderFactory();
200         keyInfoBuilder = 
201             (XMLSignatureBuilder<KeyInfo>) builderFactory.getBuilder(KeyInfo.DEFAULT_ELEMENT_NAME);
202         dataReferenceBuilder = 
203             (XMLEncryptionBuilder<DataReference>) builderFactory.getBuilder(DataReference.DEFAULT_ELEMENT_NAME);
204         referenceListBuilder = 
205             (XMLEncryptionBuilder<ReferenceList>) builderFactory.getBuilder(ReferenceList.DEFAULT_ELEMENT_NAME);
206         retrievalMethodBuilder = 
207             (XMLSignatureBuilder<RetrievalMethod>) builderFactory.getBuilder(RetrievalMethod.DEFAULT_ELEMENT_NAME);
208         keyNameBuilder = 
209             (XMLSignatureBuilder<KeyName>) builderFactory.getBuilder(KeyName.DEFAULT_ELEMENT_NAME);
210         carriedKeyNameBuilder = 
211             (XMLEncryptionBuilder<CarriedKeyName>) builderFactory.getBuilder(CarriedKeyName.DEFAULT_ELEMENT_NAME);
212         
213         try{
214             idGenerator = new SecureRandomIdentifierGenerator();
215         }catch(NoSuchAlgorithmException e){
216             log.error("JVM does not support SHA1PRNG random number generation algorithm.");
217         }
218         
219         keyPlacement = KeyPlacement.PEER;
220     }
221     
222     /**
223      * Set the generator to use when creating XML ID attribute values.
224      * 
225      * @param newIDGenerator the new IdentifierGenerator to use
226      */
227     public void setIDGenerator(IdentifierGenerator newIDGenerator) {
228         this.idGenerator = newIDGenerator;
229     }
230 
231     /**
232      * Get the current key placement option.
233      * 
234      * @return returns the key placement option.
235      */
236     public KeyPlacement getKeyPlacement() {
237         return this.keyPlacement;
238     }
239 
240     /**
241      * Set the key placement option.
242      * 
243      * @param newKeyPlacement The new key placement option to set
244      */
245     public void setKeyPlacement(KeyPlacement newKeyPlacement) {
246         this.keyPlacement = newKeyPlacement;
247     }
248 
249     /**
250      * Encrypt the specified Assertion.
251      * 
252      * @param assertion the Assertion to encrypt
253      * @return an EncryptedAssertion 
254      * @throws EncryptionException thrown when encryption generates an error
255      */
256     public EncryptedAssertion encrypt(Assertion assertion) throws EncryptionException {
257         return (EncryptedAssertion) encrypt(assertion, EncryptedAssertion.DEFAULT_ELEMENT_NAME);
258     }
259 
260     /**
261      * Encrypt the specified Assertion, treating as an identifier and returning
262      * an EncryptedID.
263      * 
264      * @param assertion the Assertion to encrypt
265      * @return an EncryptedID 
266      * @throws EncryptionException thrown when encryption generates an error
267      */
268     public EncryptedID encryptAsID(Assertion assertion) throws EncryptionException {
269         return (EncryptedID) encrypt(assertion, EncryptedID.DEFAULT_ELEMENT_NAME);
270     }
271     
272     /**
273      * Encrypt the specified Attribute.
274      * 
275      * @param attribute the Attribute to encrypt
276      * @return an EncryptedAttribute
277      * @throws EncryptionException thrown when encryption generates an error
278      */
279     public EncryptedAttribute encrypt(Attribute attribute) throws EncryptionException {
280         return (EncryptedAttribute) encrypt(attribute, EncryptedAttribute.DEFAULT_ELEMENT_NAME);
281     }
282 
283     /**
284      * Encrypt the specified NameID.
285      * 
286      * @param nameID the NameID to encrypt
287      * @return an EncryptedID
288      * @throws EncryptionException thrown when encryption generates an error
289      */
290     public EncryptedID encrypt(NameID nameID) throws EncryptionException {
291         return (EncryptedID) encrypt(nameID, EncryptedID.DEFAULT_ELEMENT_NAME);
292     }
293 
294     /**
295      * Encrypt the specified BaseID.
296      * 
297      * @param baseID the BaseID to encrypt
298      * @return an EncryptedID
299      * @throws EncryptionException thrown when encryption generates an error
300      */
301     public EncryptedID encrypt(BaseID baseID) throws EncryptionException {
302         return (EncryptedID) encrypt(baseID, EncryptedID.DEFAULT_ELEMENT_NAME);
303     }
304 
305     /**
306      * Encrypt the specified NewID.
307      * 
308      * @param newID the NewID to encrypt
309      * @return a NewEncryptedID
310      * @throws EncryptionException thrown when encryption generates an error
311      */
312     public NewEncryptedID encrypt(NewID newID) throws EncryptionException {
313         return (NewEncryptedID) encrypt(newID, NewEncryptedID.DEFAULT_ELEMENT_NAME);
314     }
315     
316     /**
317      * Encrypt the specified XMLObject, and return it as an instance of the specified QName,
318      * which should be one of the types derived from {@link org.opensaml.saml2.core.EncryptedElementType}.
319      * 
320      * @param xmlObject the XMLObject to encrypt
321      * @param encElementName the QName of the specialization of EncryptedElementType to return
322      * @return a specialization of {@link org.opensaml.saml2.core.EncryptedElementType}
323      * @throws EncryptionException thrown when encryption generates an error
324      */
325     private EncryptedElementType encrypt(XMLObject xmlObject, QName encElementName) throws EncryptionException {
326         
327         checkParams(encParams, kekParamsList);
328        
329         EncryptedElementType encElement = 
330             (EncryptedElementType) builderFactory.getBuilder(encElementName).buildObject(encElementName);
331         
332         // Marshall the containing element, we will need its Document context to pass 
333         // to the key encryption method
334         checkAndMarshall(encElement);
335         Document ownerDocument = encElement.getDOM().getOwnerDocument();
336         
337         String encryptionAlgorithmURI = encParams.getAlgorithm();
338         Key encryptionKey = SecurityHelper.extractEncryptionKey(encParams.getEncryptionCredential());
339         if (encryptionKey == null) {
340             encryptionKey = generateEncryptionKey(encryptionAlgorithmURI);
341         }
342         
343         EncryptedData encryptedData = encryptElement(xmlObject, encryptionKey, encryptionAlgorithmURI, false);
344         if (encParams.getKeyInfoGenerator() != null) {
345             KeyInfoGenerator generator = encParams.getKeyInfoGenerator();
346             log.debug("Dynamically generating KeyInfo from Credential for EncryptedData using generator: {}",
347                     generator.getClass().getName());
348             try {
349                 encryptedData.setKeyInfo( generator.generate(encParams.getEncryptionCredential()) );
350             } catch (SecurityException e) {
351                 throw new EncryptionException("Error generating EncryptedData KeyInfo", e);
352             }
353         }
354         
355         List<EncryptedKey> encryptedKeys = new ArrayList<EncryptedKey>();
356         if (kekParamsList != null && ! kekParamsList.isEmpty()) {
357             encryptedKeys.addAll( encryptKey(encryptionKey, kekParamsList, ownerDocument) );
358         }
359         
360         return processElements(encElement, encryptedData, encryptedKeys);
361     }
362 
363     /**
364      * Handle post-processing of generated EncryptedData and EncryptedKey(s) and storage in the appropriate
365      * EncryptedElementType instance.
366      * 
367      * @param encElement the EncryptedElementType instance which will hold the encrypted data and keys
368      * @param encData the EncryptedData object
369      * @param encKeys the list of EncryptedKey objects
370      * @return the processed EncryptedElementType instance
371      * 
372      * @throws EncryptionException thrown when processing encounters an error
373      */
374     protected EncryptedElementType processElements(EncryptedElementType encElement,
375             EncryptedData encData, List<EncryptedKey> encKeys) throws EncryptionException {
376         // First ensure certain elements/attributes are non-null, common to all cases.
377         if (encData.getID() == null) {
378             encData.setID(idGenerator.generateIdentifier());
379         }
380         
381         // If not doing key wrapping, just return the encrypted element
382         if (encKeys.isEmpty()) {
383             encElement.setEncryptedData(encData);
384             return encElement;
385         }
386         
387         if (encData.getKeyInfo() == null) {
388             encData.setKeyInfo(keyInfoBuilder.buildObject());
389         }
390         
391         for (EncryptedKey encKey : encKeys) {
392             if (encKey.getID() == null) {
393                 encKey.setID(idGenerator.generateIdentifier());
394             }
395         }
396         
397         switch (keyPlacement) {
398             case INLINE:
399                 return placeKeysInline(encElement, encData, encKeys);
400             case PEER:
401                 return placeKeysAsPeers(encElement, encData, encKeys);
402             default:
403                 //Shouldn't be able to get here, but just in case...
404                 throw new EncryptionException("Unsupported key placement option was specified: " + keyPlacement);
405         }
406     }
407 
408     /**
409      * Place the EncryptedKey elements inside the KeyInfo element within the EncryptedData element.
410      * 
411      * Although operationally trivial, this method is provided so that subclasses may 
412      * override or augment as desired.
413      * 
414      * @param encElement the EncryptedElementType instance which will hold the encrypted data and keys
415      * @param encData the EncryptedData object
416      * @param encKeys the list of EncryptedKey objects
417      * @return the processed EncryptedElementType instance
418      */
419     protected EncryptedElementType placeKeysInline(EncryptedElementType encElement,
420             EncryptedData encData, List<EncryptedKey> encKeys) {
421         
422         log.debug("Placing EncryptedKey elements inline inside EncryptedData");
423         
424         encData.getKeyInfo().getEncryptedKeys().addAll(encKeys);
425         encElement.setEncryptedData(encData);
426         return encElement;
427     }
428     
429     /**
430      * Store the specified EncryptedData and EncryptedKey(s) in the specified instance of EncryptedElementType
431      * as peer elements, following SAML 2 Errata E43 guidelines for forward and back referencing between the
432      * EncryptedData and EncryptedKey(s).
433      * 
434      * @param encElement a specialization of EncryptedElementType to store the encrypted data and keys
435      * @param encData the EncryptedData to store
436      * @param encKeys the EncryptedKey(s) to store
437      * @return the resulting specialization of EncryptedElementType
438      */
439     protected EncryptedElementType placeKeysAsPeers(EncryptedElementType encElement,
440             EncryptedData encData, List<EncryptedKey> encKeys) {
441         
442         log.debug("Placing EncryptedKey elements as peers of EncryptedData in EncryptedElementType");
443         
444         for (EncryptedKey encKey : encKeys) {
445             if (encKey.getReferenceList() == null) {
446                 encKey.setReferenceList(referenceListBuilder.buildObject());
447             }
448         }
449         
450         // If there is only 1 EncryptedKey we have a simple forward reference (RetrievalMethod) 
451         // and back reference (ReferenceList/DataReference) requirement.
452         // Multiple "multicast" keys use back reference + CarriedKeyName
453         if (encKeys.size() == 1) {
454             linkSinglePeerKey(encData, encKeys.get(0));
455         } else if (encKeys.size() > 1) {
456             linkMultiplePeerKeys(encData, encKeys);
457         }
458         
459         encElement.setEncryptedData(encData);
460         encElement.getEncryptedKeys().addAll(encKeys);
461         
462         return encElement;
463     }
464     
465     /**
466      * Link a single EncryptedKey to the EncryptedData according to guidelines in SAML Errata E43.
467      * 
468      * @param encData the EncryptedData
469      * @param encKey the EncryptedKey
470      */
471     protected void linkSinglePeerKey(EncryptedData encData, EncryptedKey encKey) {
472         log.debug("Linking single peer EncryptedKey with RetrievalMethod and DataReference");
473         // Forward reference from EncryptedData to the EncryptedKey
474         RetrievalMethod rm = retrievalMethodBuilder.buildObject();
475         rm.setURI("#" + encKey.getID());
476         rm.setType(EncryptionConstants.TYPE_ENCRYPTED_KEY);
477         encData.getKeyInfo().getRetrievalMethods().add(rm);
478         
479         // Back reference from the EncryptedKey to the EncryptedData
480         DataReference dr = dataReferenceBuilder.buildObject();
481         dr.setURI("#" + encData.getID());
482         encKey.getReferenceList().getDataReferences().add(dr);
483     }
484 
485     /**
486      * Link multiple "multicast" EncryptedKeys to the EncryptedData according 
487      * to guidelines in SAML Errata E43.
488      * 
489      * @param encData the EncryptedData
490      * @param encKeys the list of EncryptedKeys
491      */
492     protected void linkMultiplePeerKeys(EncryptedData encData, List<EncryptedKey> encKeys) {
493         log.debug("Linking multiple peer EncryptedKeys with CarriedKeyName and DataReference");
494         // Get the name of the data encryption key
495         List<KeyName> dataEncKeyNames = encData.getKeyInfo().getKeyNames();
496         String carriedKeyNameValue;
497         if (dataEncKeyNames.size() == 0  || DatatypeHelper.isEmpty(dataEncKeyNames.get(0).getValue()) ) {
498             // If there isn't one, autogenerate a random key name.
499             String keyNameValue = idGenerator.generateIdentifier();
500             log.debug("EncryptedData encryption key had no KeyName, generated one for use in CarriedKeyName: {}",
501                     keyNameValue);
502             
503             KeyName keyName = dataEncKeyNames.get(0);
504             if (keyName == null) {
505                 keyName = keyNameBuilder.buildObject();
506                 dataEncKeyNames.add(keyName);
507             }
508             keyName.setValue(keyNameValue);
509             carriedKeyNameValue = keyNameValue;
510         } else {
511             carriedKeyNameValue = dataEncKeyNames.get(0).getValue();
512         }
513         
514         // Set carried key name of the multicast key in each EncryptedKey
515         for (EncryptedKey encKey : encKeys) {
516             if (encKey.getCarriedKeyName() == null) {
517                 encKey.setCarriedKeyName(carriedKeyNameBuilder.buildObject());
518             }
519             encKey.getCarriedKeyName().setValue(carriedKeyNameValue);
520             
521             // Back reference from the EncryptedKeys to the EncryptedData
522             DataReference dr = dataReferenceBuilder.buildObject();
523             dr.setURI("#" + encData.getID());
524             encKey.getReferenceList().getDataReferences().add(dr);
525             
526         }
527     }
528 
529 }