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 org.apache.wss4j.common.WSS4JConstants;
23  import org.apache.wss4j.common.crypto.Crypto;
24  import org.apache.wss4j.common.crypto.CryptoType;
25  import org.apache.wss4j.common.ext.WSSecurityException;
26  import org.apache.wss4j.common.token.*;
27  import org.apache.wss4j.common.util.AttachmentUtils;
28  import org.apache.wss4j.common.util.KeyUtils;
29  import org.apache.wss4j.dom.WSConstants;
30  import org.apache.wss4j.dom.util.WSSecurityUtil;
31  import org.apache.xml.security.encryption.XMLCipherUtil;
32  import org.apache.xml.security.encryption.XMLEncryptionException;
33  import org.apache.xml.security.encryption.keys.content.AgreementMethodImpl;
34  import org.apache.xml.security.encryption.params.ConcatKDFParams;
35  import org.apache.xml.security.encryption.params.HKDFParams;
36  import org.apache.xml.security.encryption.params.KeyAgreementParameters;
37  import org.apache.xml.security.encryption.params.KeyDerivationParameters;
38  import org.apache.xml.security.exceptions.XMLSecurityException;
39  import org.apache.xml.security.stax.ext.XMLSecurityConstants;
40  import org.apache.xml.security.stax.impl.util.IDGenerator;
41  import org.apache.xml.security.utils.Constants;
42  import org.apache.xml.security.utils.XMLUtils;
43  import org.w3c.dom.Document;
44  import org.w3c.dom.Element;
45  import org.w3c.dom.Text;
46  
47  import javax.crypto.Cipher;
48  import javax.crypto.IllegalBlockSizeException;
49  import javax.crypto.SecretKey;
50  import javax.crypto.spec.OAEPParameterSpec;
51  import javax.xml.crypto.MarshalException;
52  import javax.xml.crypto.dom.DOMStructure;
53  import javax.xml.crypto.dsig.XMLSignatureFactory;
54  import javax.xml.crypto.dsig.keyinfo.KeyInfo;
55  import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
56  import javax.xml.crypto.dsig.keyinfo.KeyValue;
57  import java.security.*;
58  import java.security.cert.X509Certificate;
59  
60  /**
61   * Builder class to build an EncryptedKey.
62   *
63   * This is especially useful in the case where the same
64   * <code>EncryptedKey</code> has to be used to sign and encrypt the message In
65   * such a situation this builder will add the <code>EncryptedKey</code> to the
66   * security header and we can use the information form the builder to provide to
67   * other builders to reference to the token
68   */
69  public class WSSecEncryptedKey extends WSSecBase {
70  
71      private static final org.slf4j.Logger LOG =
72          org.slf4j.LoggerFactory.getLogger(WSSecEncryptedKey.class);
73  
74      /**
75       * Algorithm used to encrypt the ephemeral key
76       */
77      private String keyEncAlgo = WSConstants.KEYTRANSPORT_RSAOAEP;
78  
79      /**
80       * Key agreement method algorithm used to encrypt the transport key.
81       * Example for ECDH-ES: http://www.w3.org/2009/xmlenc11#ECDH-ES
82       * and xec example: X25519: http://www.w3.org/2021/04/xmldsig-more#x25519,
83       *  x448: http://www.w3.org/2021/04/xmldsig-more#x448
84       */
85      private String keyAgreementMethod;
86  
87      /**
88       * Method to derive the key to be used to encrypt the data with the Key keyAgreementMethod
89       *
90       */
91      private String keyDerivationMethod = WSS4JConstants.KEYDERIVATION_HKDF;
92  
93  
94      /**
95       * The Key Derivation Parameters for the with the Key keyAgreementMethod
96       */
97      private KeyDerivationParameters keyDerivationParameters;
98  
99      /**
100      * Digest Algorithm to be used with RSA-OAEP. The default is SHA-1 (which is not
101      * written out unless it is explicitly configured).
102      */
103     private String digestAlgo;
104 
105     /**
106      * MGF Algorithm to be used with RSA-OAEP. The default is MGF-SHA-1 (which is not
107      * written out unless it is explicitly configured).
108      */
109     private String mgfAlgo;
110 
111     /**
112      * xenc:EncryptedKey element
113      */
114     private Element encryptedKeyElement;
115 
116     /**
117      * The Token identifier of the token that the <code>DerivedKeyToken</code>
118      * is (or to be) derived from.
119      */
120     private String encKeyId;
121 
122     /**
123      * BinarySecurityToken to be included in the case where BST_DIRECT_REFERENCE
124      * is used to refer to the asymmetric encryption cert
125      */
126     private BinarySecurity bstToken;
127 
128     private X509Certificate useThisCert;
129 
130     private PublicKey useThisPublicKey;
131 
132     /**
133      * Custom token value
134      */
135     private String customEKTokenValueType;
136 
137     /**
138      * Custom token id
139      */
140     private String customEKTokenId;
141 
142     private boolean bstAddedToSecurityHeader;
143     private boolean includeEncryptionToken;
144     private Element customEKKeyInfoElement;
145     private Provider provider;
146 
147     private String encryptedKeySHA1;
148 
149     public WSSecEncryptedKey(WSSecHeader securityHeader) {
150         super(securityHeader);
151     }
152 
153     public WSSecEncryptedKey(Document doc) {
154         this(doc, null);
155     }
156 
157     public WSSecEncryptedKey(Document doc, Provider provider) {
158         super(doc);
159         this.provider = provider;
160     }
161 
162     /**
163      * Set the user name to get the encryption certificate.
164      *
165      * The public key of this certificate is used, thus no password necessary.
166      * The user name is a keystore alias usually.
167      *
168      * @param user
169      */
170     public void setUserInfo(String user) {
171         this.user = user;
172     }
173 
174     /**
175      * Get the id generated during <code>prepare()</code>.
176      *
177      * Returns the the value of wsu:Id attribute of the EncryptedKey element.
178      *
179      * @return Return the wsu:Id of this token or null if <code>prepare()</code>
180      *         was not called before.
181      */
182     public String getId() {
183         return encKeyId;
184     }
185 
186     /**
187      * Create the EncryptedKey Element for inclusion in the security header, by encrypting the
188      * symmetricKey parameter using either a public key or certificate that is set on the class,
189      * and adding the encrypted bytes as the CipherValue of the EncryptedKey element. The KeyInfo
190      * is constructed according to the keyIdentifierType and also the type of the encrypting
191      * key
192      *
193      * @param crypto An instance of the Crypto API to handle keystore and certificates
194      * @param symmetricKey The symmetric key to encrypt and insert into the EncryptedKey
195      * @throws WSSecurityException
196      */
197     public void prepare(Crypto crypto, SecretKey symmetricKey) throws WSSecurityException {
198 
199         if (useThisPublicKey != null) {
200             createEncryptedKeyElement(useThisPublicKey);
201             byte[] encryptedEphemeralKey = encryptSymmetricKey(useThisPublicKey, symmetricKey);
202             addCipherValueElement(encryptedEphemeralKey);
203         } else {
204             //
205             // Get the certificate that contains the public key for the public key
206             // algorithm that will encrypt the generated symmetric (session) key.
207             //
208             X509Certificate remoteCert = useThisCert;
209             if (remoteCert == null) {
210                 CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
211                 cryptoType.setAlias(user);
212                 if (crypto == null) {
213                     throw new WSSecurityException(
214                                                   WSSecurityException.ErrorCode.FAILURE,
215                                                   "noUserCertsFound",
216                                                   new Object[] {user, "encryption"});
217                 }
218                 X509Certificate[] certs = crypto.getX509Certificates(cryptoType);
219                 if (certs == null || certs.length <= 0) {
220                     throw new WSSecurityException(
221                                                   WSSecurityException.ErrorCode.FAILURE,
222                                                   "noUserCertsFound",
223                                                   new Object[] {user, "encryption"});
224                 }
225                 remoteCert = certs[0];
226             }
227 
228             Key kek;
229             KeyAgreementParameters dhSpec = null;
230             if (isKeyAgreementConfigured(keyAgreementMethod)) {
231                 // generate ephemeral keys the key must match receivers keys
232                 dhSpec = buildKeyAgreementParameter(remoteCert.getPublicKey());
233                 kek = generateEncryptionKey(dhSpec);
234             } else {
235                 kek = remoteCert.getPublicKey();
236             }
237 
238             createEncryptedKeyElement(remoteCert, crypto, dhSpec);
239             byte[] encryptedEphemeralKey = encryptSymmetricKey(kek, symmetricKey);
240             addCipherValueElement(encryptedEphemeralKey);
241         }
242     }
243 
244     /**
245      * Create and add the CipherValue Element to the EncryptedKey Element.
246      */
247     protected void addCipherValueElement(byte[] encryptedEphemeralKey) throws WSSecurityException {
248         Element xencCipherValue = createCipherValue(getDocument(), encryptedKeyElement);
249         if (storeBytesInAttachment) {
250             final String attachmentId = getIdAllocator().createId("", getDocument());
251             AttachmentUtils.storeBytesInAttachment(xencCipherValue, getDocument(), attachmentId,
252                                                   encryptedEphemeralKey, attachmentCallbackHandler);
253         } else {
254             Text keyText =
255                 WSSecurityUtil.createBase64EncodedTextNode(getDocument(), encryptedEphemeralKey);
256             xencCipherValue.appendChild(keyText);
257         }
258 
259         setEncryptedKeySHA1(encryptedEphemeralKey);
260     }
261 
262     /**
263      * Now we need to set up the EncryptedKey header block:
264      *  1) create a EncryptedKey element and set a wsu:Id for it
265      *  2) Generate ds:KeyInfo element
266      *  3) Create and set up the ds:KeyInfo child element - this can either be a SecurityTokenReference or X509Data/X509SKI
267      *  4) Create the CipherValue element structure and insert the encrypted session key
268      */
269     protected void createEncryptedKeyElement(X509Certificate remoteCert, Crypto crypto, KeyAgreementParameters dhSpec)
270             throws WSSecurityException {
271         encryptedKeyElement = createEncryptedKey(getDocument(), keyEncAlgo);
272         if (encKeyId == null || encKeyId.isEmpty()) {
273             encKeyId = IDGenerator.generateID("EK-");
274         }
275         encryptedKeyElement.setAttributeNS(null, "Id", encKeyId);
276 
277         if (customEKKeyInfoElement != null) {
278             encryptedKeyElement.appendChild(getDocument().adoptNode(customEKKeyInfoElement));
279         } else if (keyIdentifierType == WSConstants.X509_SKI) {
280             DOMX509SKI x509SKI = new DOMX509SKI(getDocument(), remoteCert);
281             DOMX509Data x509Data = new DOMX509Data(getDocument(), x509SKI);
282 
283             Element keyInfoElement = createKeyInfoElement(x509Data.getElement(), dhSpec);
284             encryptedKeyElement.appendChild(keyInfoElement);
285         } else {
286             SecurityTokenReference secToken = new SecurityTokenReference(getDocument());
287             if (addWSUNamespace) {
288                 secToken.addWSUNamespace();
289             }
290 
291             switch (keyIdentifierType) {
292             case WSConstants.X509_KEY_IDENTIFIER:
293                 secToken.setKeyIdentifier(remoteCert);
294                 break;
295 
296             case WSConstants.SKI_KEY_IDENTIFIER:
297                 secToken.setKeyIdentifierSKI(remoteCert, crypto);
298 
299                 if (includeEncryptionToken) {
300                     addBST(remoteCert);
301                 }
302                 break;
303 
304             case WSConstants.THUMBPRINT_IDENTIFIER:
305             case WSConstants.ENCRYPTED_KEY_SHA1_IDENTIFIER:
306                 //
307                 // This identifier is not applicable for this case, so fall back to
308                 // ThumbprintRSA.
309                 //
310                 secToken.setKeyIdentifierThumb(remoteCert);
311 
312                 if (includeEncryptionToken) {
313                     addBST(remoteCert);
314                 }
315                 break;
316 
317             case WSConstants.ISSUER_SERIAL:
318                 addIssuerSerial(remoteCert, secToken, false);
319                 break;
320 
321             case WSConstants.ISSUER_SERIAL_QUOTE_FORMAT:
322                 addIssuerSerial(remoteCert, secToken,true);
323                 break;
324 
325             case WSConstants.BST_DIRECT_REFERENCE:
326                 Reference ref = new Reference(getDocument());
327                 String certUri = IDGenerator.generateID(null);
328                 ref.setURI("#" + certUri);
329                 bstToken = new X509Security(getDocument());
330                 ((X509Security) bstToken).setX509Certificate(remoteCert);
331                 bstToken.setID(certUri);
332                 ref.setValueType(bstToken.getValueType());
333                 secToken.setReference(ref);
334                 break;
335 
336             case WSConstants.CUSTOM_SYMM_SIGNING :
337                 Reference refCust = new Reference(getDocument());
338                 if (WSConstants.WSS_SAML_KI_VALUE_TYPE.equals(customEKTokenValueType)) {
339                     secToken.addTokenType(WSConstants.WSS_SAML_TOKEN_TYPE);
340                     refCust.setValueType(customEKTokenValueType);
341                 } else if (WSConstants.WSS_SAML2_KI_VALUE_TYPE.equals(customEKTokenValueType)) {
342                     secToken.addTokenType(WSConstants.WSS_SAML2_TOKEN_TYPE);
343                 } else if (WSConstants.WSS_ENC_KEY_VALUE_TYPE.equals(customEKTokenValueType)) {
344                     secToken.addTokenType(WSConstants.WSS_ENC_KEY_VALUE_TYPE);
345                     refCust.setValueType(customEKTokenValueType);
346                 } else {
347                     refCust.setValueType(customEKTokenValueType);
348                 }
349                 refCust.setURI("#" + customEKTokenId);
350                 secToken.setReference(refCust);
351                 break;
352 
353             case WSConstants.CUSTOM_SYMM_SIGNING_DIRECT :
354                 Reference refCustd = new Reference(getDocument());
355                 if (WSConstants.WSS_SAML_KI_VALUE_TYPE.equals(customEKTokenValueType)) {
356                     secToken.addTokenType(WSConstants.WSS_SAML_TOKEN_TYPE);
357                     refCustd.setValueType(customEKTokenValueType);
358                 } else if (WSConstants.WSS_SAML2_KI_VALUE_TYPE.equals(customEKTokenValueType)) {
359                     secToken.addTokenType(WSConstants.WSS_SAML2_TOKEN_TYPE);
360                 } else if (WSConstants.WSS_ENC_KEY_VALUE_TYPE.equals(customEKTokenValueType)) {
361                     secToken.addTokenType(WSConstants.WSS_ENC_KEY_VALUE_TYPE);
362                     refCustd.setValueType(customEKTokenValueType);
363                 } else {
364                     refCustd.setValueType(customEKTokenValueType);
365                 }
366                 refCustd.setURI(customEKTokenId);
367                 secToken.setReference(refCustd);
368                 break;
369 
370             case WSConstants.CUSTOM_KEY_IDENTIFIER:
371                 secToken.setKeyIdentifier(customEKTokenValueType, customEKTokenId);
372                 if (WSConstants.WSS_SAML_KI_VALUE_TYPE.equals(customEKTokenValueType)) {
373                     secToken.addTokenType(WSConstants.WSS_SAML_TOKEN_TYPE);
374                 } else if (WSConstants.WSS_SAML2_KI_VALUE_TYPE.equals(customEKTokenValueType)) {
375                     secToken.addTokenType(WSConstants.WSS_SAML2_TOKEN_TYPE);
376                 } else if (WSConstants.WSS_ENC_KEY_VALUE_TYPE.equals(customEKTokenValueType)) {
377                     secToken.addTokenType(WSConstants.WSS_ENC_KEY_VALUE_TYPE);
378                 } else if (SecurityTokenReference.ENC_KEY_SHA1_URI.equals(customEKTokenValueType)) {
379                     secToken.addTokenType(WSConstants.WSS_ENC_KEY_VALUE_TYPE);
380                 }
381                 break;
382 
383             default:
384                 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "unsupportedKeyId",
385                                               new Object[] {keyIdentifierType});
386             }
387 
388             Element keyInfoElement = createKeyInfoElement(secToken.getElement(), dhSpec);
389             encryptedKeyElement.appendChild(keyInfoElement);
390         }
391     }
392 
393     /**
394      * Method builds and returns a ds:KeyInfo element wrapping the provided child element.
395      */
396     private Element createKeyInfoElement(Element childElement, KeyAgreementParameters dhSpec) throws WSSecurityException {
397         Element keyInfoElement =
398                 getDocument().createElementNS(
399                         WSConstants.SIG_NS, WSConstants.SIG_PREFIX + ":" + WSConstants.KEYINFO_LN
400                 );
401         keyInfoElement.setAttributeNS(
402                 WSConstants.XMLNS_NS, "xmlns:" + WSConstants.SIG_PREFIX, WSConstants.SIG_NS
403         );
404         if (isKeyAgreementConfigured(keyAgreementMethod)) {
405             try {
406                 AgreementMethodImpl agreementMethod = new AgreementMethodImpl(getDocument(), dhSpec);
407                 agreementMethod.getRecipientKeyInfo().addUnknownElement(childElement);
408                 Element agreementMethodElement = agreementMethod.getElement();
409                 keyInfoElement.appendChild(agreementMethodElement);
410             } catch (XMLSecurityException e) {
411                 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "unsupportedKeyId",
412                                               new Object[] {keyIdentifierType});
413             }
414 
415         } else {
416             keyInfoElement.appendChild(childElement);
417         }
418         return keyInfoElement;
419     }
420 
421     /**
422      * Method verifies is the key agreement method is configured(not empty)
423      * @param keyAgreementMethod the key agreement method
424      * @return true if the key agreement method is not empty else false
425      */
426     private boolean isKeyAgreementConfigured(String keyAgreementMethod) {
427          return keyAgreementMethod != null && !keyAgreementMethod.isEmpty();
428     }
429 
430     private void addIssuerSerial(X509Certificate remoteCert, SecurityTokenReference secToken, boolean isCommaDelimited)
431             throws WSSecurityException {
432         String issuer = remoteCert.getIssuerX500Principal().getName();
433         java.math.BigInteger serialNumber = remoteCert.getSerialNumber();
434         DOMX509IssuerSerial domIssuerSerial =
435                 new DOMX509IssuerSerial(getDocument(), issuer, serialNumber, isCommaDelimited);
436         DOMX509Data domX509Data = new DOMX509Data(getDocument(), domIssuerSerial);
437         secToken.setUnknownElement(domX509Data.getElement());
438 
439         if (includeEncryptionToken) {
440             addBST(remoteCert);
441         }
442     }
443 
444     /**
445      * Now we need to setup the EncryptedKey header block:
446      *  1) create a EncryptedKey element and set a wsu:Id for it
447      *  2) Generate ds:KeyInfo element, this wraps the wsse:SecurityTokenReference
448      *  3) Create and set up the SecurityTokenReference according to the keyIdentifier parameter
449      *  4) Create the CipherValue element structure and insert the encrypted session key
450      */
451     protected void createEncryptedKeyElement(Key key) throws WSSecurityException {
452         encryptedKeyElement = createEncryptedKey(getDocument(), keyEncAlgo);
453         if (encKeyId == null || encKeyId.isEmpty()) {
454             encKeyId = IDGenerator.generateID("EK-");
455         }
456         encryptedKeyElement.setAttributeNS(null, "Id", encKeyId);
457 
458         if (customEKKeyInfoElement != null) {
459             encryptedKeyElement.appendChild(getDocument().adoptNode(customEKKeyInfoElement));
460         } else {
461             SecurityTokenReference secToken = new SecurityTokenReference(getDocument());
462             if (addWSUNamespace) {
463                 secToken.addWSUNamespace();
464             }
465 
466             switch (keyIdentifierType) {
467 
468                 case WSConstants.CUSTOM_SYMM_SIGNING :
469                     Reference refCust = new Reference(getDocument());
470                     if (WSConstants.WSS_SAML_KI_VALUE_TYPE.equals(customEKTokenValueType)) {
471                         secToken.addTokenType(WSConstants.WSS_SAML_TOKEN_TYPE);
472                         refCust.setValueType(customEKTokenValueType);
473                     } else if (WSConstants.WSS_SAML2_KI_VALUE_TYPE.equals(customEKTokenValueType)) {
474                         secToken.addTokenType(WSConstants.WSS_SAML2_TOKEN_TYPE);
475                     } else if (WSConstants.WSS_ENC_KEY_VALUE_TYPE.equals(customEKTokenValueType)) {
476                         secToken.addTokenType(WSConstants.WSS_ENC_KEY_VALUE_TYPE);
477                         refCust.setValueType(customEKTokenValueType);
478                     } else {
479                         refCust.setValueType(customEKTokenValueType);
480                     }
481                     refCust.setURI("#" + customEKTokenId);
482                     secToken.setReference(refCust);
483                     break;
484 
485                 case WSConstants.CUSTOM_SYMM_SIGNING_DIRECT :
486                     Reference refCustd = new Reference(getDocument());
487                     if (WSConstants.WSS_SAML_KI_VALUE_TYPE.equals(customEKTokenValueType)) {
488                         secToken.addTokenType(WSConstants.WSS_SAML_TOKEN_TYPE);
489                         refCustd.setValueType(customEKTokenValueType);
490                     } else if (WSConstants.WSS_SAML2_KI_VALUE_TYPE.equals(customEKTokenValueType)) {
491                         secToken.addTokenType(WSConstants.WSS_SAML2_TOKEN_TYPE);
492                     } else if (WSConstants.WSS_ENC_KEY_VALUE_TYPE.equals(customEKTokenValueType)) {
493                         secToken.addTokenType(WSConstants.WSS_ENC_KEY_VALUE_TYPE);
494                         refCustd.setValueType(customEKTokenValueType);
495                     } else {
496                         refCustd.setValueType(customEKTokenValueType);
497                     }
498                     refCustd.setURI(customEKTokenId);
499                     secToken.setReference(refCustd);
500                     break;
501 
502                 case WSConstants.CUSTOM_KEY_IDENTIFIER:
503                     secToken.setKeyIdentifier(customEKTokenValueType, customEKTokenId);
504                     if (WSConstants.WSS_SAML_KI_VALUE_TYPE.equals(customEKTokenValueType)) {
505                         secToken.addTokenType(WSConstants.WSS_SAML_TOKEN_TYPE);
506                     } else if (WSConstants.WSS_SAML2_KI_VALUE_TYPE.equals(customEKTokenValueType)) {
507                         secToken.addTokenType(WSConstants.WSS_SAML2_TOKEN_TYPE);
508                     } else if (WSConstants.WSS_ENC_KEY_VALUE_TYPE.equals(customEKTokenValueType)) {
509                         secToken.addTokenType(WSConstants.WSS_ENC_KEY_VALUE_TYPE);
510                     } else if (SecurityTokenReference.ENC_KEY_SHA1_URI.equals(customEKTokenValueType)) {
511                         secToken.addTokenType(WSConstants.WSS_ENC_KEY_VALUE_TYPE);
512                     }
513                     break;
514 
515                 case WSConstants.KEY_VALUE:
516                     // This is only applicable for the PublicKey case
517                     if (!(key instanceof PublicKey)) {
518                         throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "unsupportedKeyId",
519                                                       new Object[] {keyIdentifierType});
520                     }
521                     try {
522                         XMLSignatureFactory signatureFactory;
523                         if (provider == null) {
524                             // Try to install the Santuario Provider - fall back to the JDK provider if this does
525                             // not work
526                             try {
527                                 signatureFactory = XMLSignatureFactory.getInstance("DOM", "ApacheXMLDSig");
528                             } catch (NoSuchProviderException ex) {
529                                 signatureFactory = XMLSignatureFactory.getInstance("DOM");
530                             }
531                         } else {
532                             signatureFactory = XMLSignatureFactory.getInstance("DOM", provider);
533                         }
534 
535                         KeyInfoFactory keyInfoFactory = signatureFactory.getKeyInfoFactory();
536                         KeyValue keyValue = keyInfoFactory.newKeyValue((PublicKey)key);
537                         String keyInfoUri = getIdAllocator().createSecureId("KI-", null);
538                         KeyInfo keyInfo =
539                             keyInfoFactory.newKeyInfo(
540                                 java.util.Collections.singletonList(keyValue), keyInfoUri
541                             );
542 
543                         keyInfo.marshal(new DOMStructure(encryptedKeyElement), null);
544                     } catch (java.security.KeyException | MarshalException ex) {
545                         LOG.error("", ex);
546                         throw new WSSecurityException(
547                             WSSecurityException.ErrorCode.FAILED_ENCRYPTION, ex
548                         );
549                     }
550                     break;
551 
552                 default:
553                     throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "unsupportedKeyId",
554                                                   new Object[] {keyIdentifierType});
555             }
556 
557             if (WSConstants.KEY_VALUE != keyIdentifierType) {
558                 Element keyInfoElement =
559                     getDocument().createElementNS(
560                         WSConstants.SIG_NS, WSConstants.SIG_PREFIX + ":" + WSConstants.KEYINFO_LN
561                     );
562                 keyInfoElement.setAttributeNS(
563                     WSConstants.XMLNS_NS, "xmlns:" + WSConstants.SIG_PREFIX, WSConstants.SIG_NS
564                 );
565                 keyInfoElement.appendChild(secToken.getElement());
566                 encryptedKeyElement.appendChild(keyInfoElement);
567             }
568         }
569     }
570 
571     /**
572      * Method builds the KeyAgreementParameterSpec for the ECDH-ES, X25519 or X448
573      * Key Agreement Method using the recipient's public key and preconfigured values:
574      * keyEncAlgo, digestAlgo and keyAgreementMethod. If the keyDerivationParameters
575      * are not set, the default values for the key derivation method are used.
576      *
577      * @param recipientPublicKey the recipient's public key
578      * @return KeyAgreementParameterSpec the {@link java.security.spec.AlgorithmParameterSpec} for generating the
579      * key for encrypting transport key and generating XML elements.
580      *
581      * @throws WSSecurityException if the KeyAgreementParameterSpec cannot be created due to
582      * invalid key agreement method or key derivation method and wrap algorithm not supported
583      */
584     private KeyAgreementParameters buildKeyAgreementParameter(PublicKey recipientPublicKey)
585             throws WSSecurityException {
586         KeyAgreementParameters dhSpec;
587         try {
588             int keyBitLength = org.apache.xml.security.utils.KeyUtils.getAESKeyBitSizeForWrapAlgorithm(keyEncAlgo);
589             KeyDerivationParameters kdf = keyDerivationParameters;
590             if (keyDerivationParameters == null) {
591                 LOG.debug("Set default KeyDerivationParameters for key derivation method: [{}]",
592                         keyDerivationMethod);
593                 kdf = buildDefaultKeyDerivationParameters(keyBitLength);
594             }
595             KeyPair dhKeyPair = org.apache.xml.security.utils.KeyUtils.generateEphemeralDHKeyPair(recipientPublicKey, null);
596             dhSpec = XMLCipherUtil.constructAgreementParameters(keyAgreementMethod,
597                     KeyAgreementParameters.ActorType.ORIGINATOR, kdf, null, recipientPublicKey);
598             dhSpec.setOriginatorKeyPair(dhKeyPair);
599         } catch (XMLEncryptionException e) {
600             throw new WSSecurityException(
601                     WSSecurityException.ErrorCode.FAILED_ENCRYPTION, e
602             );
603         }
604         return dhSpec;
605     }
606 
607     /**
608      * Method builds the KeyDerivationParameters for keyDerivationMethod with default values.
609      * The default values for the key derivation method are:
610      * <ul>
611      *   <li>ConcatKDF
612      *     <ul>
613      *       <li> DigestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha256"</li>
614      *       <li> AlgorithmID: "0000"</li>
615      *       <li> PartyUInfo: ""</li>
616      *       <li> PartyVInfo: ""</li>
617      *       <li> SuppPubInfo: null</li>
618      *       <li> SuppPrivInfo: null</li>
619      *     </ul>
620      *   <li>HKDF: SHA-256
621      *     <ul>
622      *       <li> PRF: http://www.w3.org/2001/04/xmldsig-more#hmac-sha256 </li>
623      *       <li> Salt: random 256 bit value</li>
624      *       <li> Info: null</li>
625      *     </ul>
626      *   </li>
627      * </ul>
628      *
629      * @param keyBitLength the length of the derived key in bits
630      * @return KeyDerivationParameters the {@link KeyDerivationParameters} for generating the
631      * key for encrypting transport key and generating XML elements.
632      * @throws WSSecurityException if the KeyDerivationParameters cannot be created
633      */
634     private KeyDerivationParameters buildDefaultKeyDerivationParameters(int keyBitLength) throws WSSecurityException {
635 
636         switch (keyDerivationMethod) {
637             case WSS4JConstants.KEYDERIVATION_CONCATKDF:
638                 return ConcatKDFParams.createBuilder(keyBitLength, WSConstants.SHA256)
639                         .algorithmID("0000")
640                         .partyUInfo("")
641                         .partyVInfo("")
642                         .build();
643             case WSS4JConstants.KEYDERIVATION_HKDF:
644                 // use semi random value for salt.
645                 // rfc5869: Yet, even a salt value of less quality (shorter in
646                 //   size or with limited entropy) may still make a significant
647                 //   contribution to the security of the output keying material
648                 byte[] semiRandom;
649                 try { 
650                     int length = keyBitLength / 8;
651                     semiRandom = XMLSecurityConstants.generateBytes(length);
652                 } catch (Exception ex) {
653                     throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex,
654                             "empty", new Object[] {"Error in generating secret bytes " }
655                     );
656                 }
657                 return HKDFParams.createBuilder(keyBitLength, WSS4JConstants.HMAC_SHA256)
658                         .salt(semiRandom)
659                         .info(null)
660                         .build();
661             default:
662                 throw new WSSecurityException(
663                         WSSecurityException.ErrorCode.FAILED_ENCRYPTION, "unsupportedKeyDerivationMethod",
664                         new Object[]{keyDerivationMethod}
665                 );
666         }
667     }
668 
669 
670     /**
671      * Method generates the key for encrypting the transport key using the KeyAgreementParameterSpec
672      *
673      * @param keyAgreementParameter the {@link KeyAgreementParameters} for generating the secret key
674      * @return SecretKey the secret key for encrypting the transport key
675      * @throws WSSecurityException if the secret key cannot be generated
676      */
677     private SecretKey generateEncryptionKey(KeyAgreementParameters keyAgreementParameter) throws WSSecurityException {
678         try {
679             // derive the key for encryption of the transport key
680             return org.apache.xml.security.utils.KeyUtils.aesWrapKeyWithDHGeneratedKey(keyAgreementParameter);
681         } catch (XMLEncryptionException e) {
682             throw new WSSecurityException(
683                     WSSecurityException.ErrorCode.FAILED_ENCRYPTION, e
684             );
685         }
686     }
687 
688     private byte[] encryptSymmetricKey(Key encryptingKey, SecretKey keyToBeEncrypted)
689         throws WSSecurityException {
690         Cipher cipher = KeyUtils.getCipherInstance(keyEncAlgo);
691         try {
692             OAEPParameterSpec oaepParameterSpec = null;
693             if (WSConstants.KEYTRANSPORT_RSAOAEP.equals(keyEncAlgo)
694                     || WSConstants.KEYTRANSPORT_RSAOAEP_XENC11.equals(keyEncAlgo)) {
695                 oaepParameterSpec = XMLCipherUtil.constructOAEPParameters(keyEncAlgo, digestAlgo, mgfAlgo, null);
696             }
697             if (oaepParameterSpec == null) {
698                 cipher.init(Cipher.WRAP_MODE, encryptingKey);
699             } else {
700                 cipher.init(Cipher.WRAP_MODE, encryptingKey, oaepParameterSpec);
701             }
702         } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
703             throw new WSSecurityException(
704                 WSSecurityException.ErrorCode.FAILED_ENCRYPTION, e
705             );
706         }
707         int blockSize = cipher.getBlockSize();
708         LOG.debug("cipher blksize: {}", blockSize);
709 
710         try {
711             return cipher.wrap(keyToBeEncrypted);
712         } catch (IllegalStateException | IllegalBlockSizeException | InvalidKeyException ex) {
713             throw new WSSecurityException(
714                 WSSecurityException.ErrorCode.FAILED_ENCRYPTION, ex
715             );
716         }
717     }
718 
719     /**
720      * Add a BinarySecurityToken
721      */
722     private void addBST(X509Certificate cert) throws WSSecurityException {
723         bstToken = new X509Security(getDocument());
724         ((X509Security) bstToken).setX509Certificate(cert);
725 
726         bstAddedToSecurityHeader = false;
727         bstToken.setID(IDGenerator.generateID(null));
728         if (addWSUNamespace) {
729             bstToken.addWSUNamespace();
730         }
731     }
732 
733     /**
734      * Create DOM subtree for <code>xenc:EncryptedKey</code>
735      *
736      * @param doc the SOAP envelope parent document
737      * @param keyTransportAlgo specifies which algorithm to use to encrypt the symmetric key
738      * @return an <code>xenc:EncryptedKey</code> element
739      */
740     private Element createEncryptedKey(Document doc, String keyTransportAlgo) {
741         Element encryptedKey =
742             doc.createElementNS(WSConstants.ENC_NS, WSConstants.ENC_PREFIX + ":EncryptedKey");
743 
744         org.apache.wss4j.common.util.XMLUtils.setNamespace(encryptedKey, WSConstants.ENC_NS, WSConstants.ENC_PREFIX);
745         Element encryptionMethod =
746             doc.createElementNS(WSConstants.ENC_NS, WSConstants.ENC_PREFIX + ":EncryptionMethod");
747         encryptionMethod.setAttributeNS(null, "Algorithm", keyTransportAlgo);
748 
749         if ((WSConstants.KEYTRANSPORT_RSAOAEP_XENC11.equals(keyEncAlgo) || WSConstants.KEYTRANSPORT_RSAOAEP.equals(
750                 keyEncAlgo)) && digestAlgo != null) {
751             Element digestElement =
752                 XMLUtils.createElementInSignatureSpace(doc, Constants._TAG_DIGESTMETHOD);
753             digestElement.setAttributeNS(null, "Algorithm", digestAlgo);
754             encryptionMethod.appendChild(digestElement);
755         }
756         if (WSConstants.KEYTRANSPORT_RSAOAEP_XENC11.equals(keyEncAlgo) && mgfAlgo != null) {
757             Element mgfElement =
758                 doc.createElementNS(WSConstants.ENC11_NS, WSConstants.ENC11_PREFIX + ":MGF");
759             mgfElement.setAttributeNS(null, "Algorithm", mgfAlgo);
760             encryptionMethod.appendChild(mgfElement);
761         }
762 
763         encryptedKey.appendChild(encryptionMethod);
764         return encryptedKey;
765     }
766 
767     protected Element createCipherValue(Document doc, Element encryptedKey) {
768         Element cipherData =
769             doc.createElementNS(WSConstants.ENC_NS, WSConstants.ENC_PREFIX + ":CipherData");
770         Element cipherValue =
771             doc.createElementNS(WSConstants.ENC_NS, WSConstants.ENC_PREFIX + ":CipherValue");
772         cipherData.appendChild(cipherValue);
773         encryptedKey.appendChild(cipherData);
774         return cipherValue;
775     }
776 
777     /**
778      * Prepend the EncryptedKey element to the elements already in the Security
779      * header.
780      *
781      * The method can be called any time after <code>prepare()</code>. This
782      * allows to insert the EncryptedKey element at any position in the Security
783      * header.
784      */
785     public void prependToHeader() {
786         Element secHeaderElement = getSecurityHeader().getSecurityHeaderElement();
787         WSSecurityUtil.prependChildElement(secHeaderElement, encryptedKeyElement);
788     }
789 
790     /**
791      * Append the EncryptedKey element to the elements already in the Security
792      * header.
793      *
794      * The method can be called any time after <code>prepare()</code>. This
795      * allows to insert the EncryptedKey element at any position in the Security
796      * header.
797      */
798     public void appendToHeader() {
799         Element secHeaderElement = getSecurityHeader().getSecurityHeaderElement();
800         secHeaderElement.appendChild(encryptedKeyElement);
801     }
802 
803     /**
804      * Prepend the BinarySecurityToken to the elements already in the Security
805      * header.
806      *
807      * The method can be called any time after <code>prepare()</code>. This
808      * allows to insert the BST element at any position in the Security header.
809      */
810     public void prependBSTElementToHeader() {
811         if (bstToken != null && !bstAddedToSecurityHeader) {
812             Element secHeaderElement = getSecurityHeader().getSecurityHeaderElement();
813             WSSecurityUtil.prependChildElement(secHeaderElement, bstToken.getElement());
814             bstAddedToSecurityHeader = true;
815         }
816     }
817 
818     /**
819      * Append the BinarySecurityToken to the elements already in the Security
820      * header.
821      *
822      * The method can be called any time after <code>prepare()</code>. This
823      * allows to insert the BST element at any position in the Security header.
824      */
825     public void appendBSTElementToHeader() {
826         if (bstToken != null && !bstAddedToSecurityHeader) {
827             Element secHeaderElement = getSecurityHeader().getSecurityHeaderElement();
828             secHeaderElement.appendChild(bstToken.getElement());
829             bstAddedToSecurityHeader = true;
830         }
831     }
832 
833     /**
834      * Set the X509 Certificate to use for encryption.
835      *
836      * If this is set <b>and</b> the key identifier is set to
837      * <code>DirectReference</code> then use this certificate to get the
838      * public key for encryption.
839      *
840      * @param cert is the X509 certificate to use for encryption
841      */
842     public void setUseThisCert(X509Certificate cert) {
843         useThisCert = cert;
844     }
845 
846     public X509Certificate getUseThisCert() {
847         return useThisCert;
848     }
849 
850     /**
851      * Set the PublicKey to use for encryption.
852      * @param key the PublicKey instance to use for encryption
853      */
854     public void setUseThisPublicKey(PublicKey key) {
855         useThisPublicKey = key;
856     }
857 
858     public PublicKey getUseThisPublicKey() {
859         return useThisPublicKey;
860     }
861 
862     /**
863      * @return Returns the encryptedKeyElement.
864      */
865     public Element getEncryptedKeyElement() {
866         return encryptedKeyElement;
867     }
868 
869     /**
870      * Set the encrypted key element when a pre prepared encrypted key is used
871      * @param encryptedKeyElement EncryptedKey element of the encrypted key used
872      */
873     public void setEncryptedKeyElement(Element encryptedKeyElement) {
874         this.encryptedKeyElement = encryptedKeyElement;
875     }
876 
877     /**
878      * @return Returns the BinarySecurityToken element.
879      */
880     public Element getBinarySecurityTokenElement() {
881         if (bstToken != null) {
882             return bstToken.getElement();
883         }
884         return null;
885     }
886 
887     public void setKeyEncAlgo(String keyEncAlgo) {
888         this.keyEncAlgo = keyEncAlgo;
889     }
890 
891     public String getKeyEncAlgo() {
892         return keyEncAlgo;
893     }
894 
895     public String getKeyAgreementMethod() {
896         return keyAgreementMethod;
897     }
898 
899     public void setKeyAgreementMethod(String keyAgreementMethod) {
900         this.keyAgreementMethod = keyAgreementMethod;
901     }
902 
903     public String getKeyDerivationMethod() {
904         return keyDerivationMethod;
905     }
906 
907     public void setKeyDerivationMethod(String keyDerivationMethod) {
908         this.keyDerivationMethod = keyDerivationMethod;
909     }
910 
911     public KeyDerivationParameters getKeyDerivationParameters() {
912         return keyDerivationParameters;
913     }
914 
915     public void setKeyDerivationParameters(KeyDerivationParameters keyDerivationParameters) {
916         this.keyDerivationParameters = keyDerivationParameters;
917     }
918 
919     /**
920      * Get the id of the BSt generated during <code>prepare()</code>.
921      *
922      * @return Returns the the value of wsu:Id attribute of the
923      * BinaruSecurityToken element.
924      */
925     public String getBSTTokenId() {
926         if (bstToken == null) {
927             return null;
928         }
929 
930         return bstToken.getID();
931     }
932 
933     /**
934      * @param encKeyId The encKeyId to set.
935      */
936     public void setEncKeyId(String encKeyId) {
937         this.encKeyId = encKeyId;
938     }
939 
940     public boolean isCertSet() {
941         return useThisCert != null;
942     }
943 
944     public void setCustomEKTokenValueType(String customEKTokenValueType) {
945         this.customEKTokenValueType = customEKTokenValueType;
946     }
947 
948     public void setCustomEKTokenId(String customEKTokenId) {
949         this.customEKTokenId = customEKTokenId;
950     }
951 
952     /**
953      * Set the digest algorithm to use with the RSA-OAEP key transport algorithm. The
954      * default is SHA-1.
955      *
956      * @param digestAlgorithm the digest algorithm to use with the RSA-OAEP key transport algorithm
957      */
958     public void setDigestAlgorithm(String digestAlgorithm) {
959         this.digestAlgo = digestAlgorithm;
960     }
961 
962     /**
963      * Get the digest algorithm to use with the RSA-OAEP key transport algorithm. The
964      * default is SHA-1.
965      */
966     public String getDigestAlgorithm() {
967         return digestAlgo;
968     }
969 
970     /**
971      * Set the MGF algorithm to use with the RSA-OAEP key transport algorithm. The
972      * default is MGF-SHA-1.
973      *
974      * @param mgfAlgorithm the MGF algorithm to use with the RSA-OAEP key transport algorithm
975      */
976     public void setMGFAlgorithm(String mgfAlgorithm) {
977         this.mgfAlgo = mgfAlgorithm;
978     }
979 
980     /**
981      * Get the MGF algorithm to use with the RSA-OAEP key transport algorithm. The
982      * default is MGF-SHA-1.
983      */
984     public String getMGFAlgorithm() {
985         return mgfAlgo;
986     }
987 
988     public boolean isIncludeEncryptionToken() {
989         return includeEncryptionToken;
990     }
991 
992     public void setIncludeEncryptionToken(boolean includeEncryptionToken) {
993         this.includeEncryptionToken = includeEncryptionToken;
994     }
995 
996     public Element getCustomEKKeyInfoElement() {
997         return customEKKeyInfoElement;
998     }
999 
1000     public void setCustomEKKeyInfoElement(Element customEKKeyInfoElement) {
1001         this.customEKKeyInfoElement = customEKKeyInfoElement;
1002     }
1003 
1004     protected void setEncryptedKeySHA1(byte[] encryptedEphemeralKey) throws WSSecurityException {
1005         byte[] encodedBytes = KeyUtils.generateDigest(encryptedEphemeralKey);
1006         encryptedKeySHA1 = XMLUtils.encodeToString(encodedBytes);
1007     }
1008 
1009     public String getEncryptedKeySHA1() {
1010         return encryptedKeySHA1;
1011     }
1012 }