This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch 3_0_x-fixes in repository https://gitbox.apache.org/repos/asf/ws-wss4j.git
commit b26872be9bcfa9299cb6f75aa48cdcdd1f6a8a2a Author: jrihtarsic <[email protected]> AuthorDate: Mon Jul 15 15:15:13 2024 +0200 [WSS-710] Implementation of the configurations for KeyDerivation functions (#298) * [WSS-710] Implementation of the configuration options to set KeyDerivation parameters * The PR updates * Changed Random with the SecureRandom for the Default HKDF salt parameter * Update PR with the lasted xmlsec updated * Add key derivation function URI validation with the AlgorithmSuiteValidator --------- Co-authored-by: RIHTARSIC Joze <[email protected]> --- parent/pom.xml | 2 +- .../wss4j/common/ConfigurationConstants.java | 38 ++++++ .../apache/wss4j/common/EncryptionActionToken.java | 20 +++ .../org/apache/wss4j/common/WSS4JConstants.java | 8 +- .../common/crypto/AlgorithmSuiteValidator.java | 20 +++ .../apache/wss4j/dom/action/EncryptionAction.java | 11 ++ .../org/apache/wss4j/dom/handler/WSHandler.java | 17 ++- .../wss4j/dom/message/WSSecEncryptedKey.java | 151 +++++++++++++++++---- .../wss4j/dom/processor/EncryptedKeyProcessor.java | 30 +++- .../apache/wss4j/dom/message/EncryptionTest.java | 137 +++++++++++++++++-- 10 files changed, 389 insertions(+), 45 deletions(-) diff --git a/parent/pom.xml b/parent/pom.xml index 99934bbbd..44bd688dc 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -48,7 +48,7 @@ <wsdl4j.version>1.6.3</wsdl4j.version> <woodstox.version>6.5.1</woodstox.version> <xz.version>1.9</xz.version> - <xmlsec.version>3.0.4</xmlsec.version> + <xmlsec.version>3.0.5-SNAPSHOT</xmlsec.version> <xmlunit.version>2.10.0</xmlunit.version> <!-- OSGi related properties --> <wss4j.osgi.import /> diff --git a/ws-security-common/src/main/java/org/apache/wss4j/common/ConfigurationConstants.java b/ws-security-common/src/main/java/org/apache/wss4j/common/ConfigurationConstants.java index 131ea60c8..3eb0fa520 100644 --- a/ws-security-common/src/main/java/org/apache/wss4j/common/ConfigurationConstants.java +++ b/ws-security-common/src/main/java/org/apache/wss4j/common/ConfigurationConstants.java @@ -752,6 +752,44 @@ public class ConfigurationConstants { */ public static final String ENC_KEY_AGREEMENT_METHOD = "encryptionKeyAgreementMethod"; + /** + * Defines the Key Derivation algorithm to derive encryption key used with the keyAgreement method. + * The default algorithm is: + * "http://www.w3.org/2021/04/xmldsig-more#hkdf" + * + * <p/> + * The application may set this parameter using the following method: + * <pre> + * call.setProperty(ConfigurationConstants.ENC_KEY_DERIVATION_FUNCTION, + * WSConstants.KEYDERIVATION_HKDF); + * </pre> + * + */ + public static final String ENC_KEY_DERIVATION_FUNCTION = "encryptionKeyDerivationFunction"; + + /** + * Defines the Key Derivation parameters to derive encryption key used with the keyAgreement method. In case the + * property value is set it supersede the ENC_KEY_DERIVATION_FUNCTION value. + * The value for the property must implement the <code>org.apache.xml.security.encryption.params.KeyDerivationParameters</code> + * interface. Currently, only the <code>org.apache.xml.security.encryption.params.HKDFParams</code> and + * <code>org.apache.xml.security.encryption.params.ConcatKDFParams</code>. + * + * + * The application may set this parameter using the following method: + * <pre> + * KeyDerivationParameters kdfParams = new ConcatKDFParams(keyBitLen, MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA256); + * kdfParams.setAlgorithmId("00363532534541"); + * kdfParams.setPartyUInfo("00DFC9DB773C588F8F"); + * kdfParams.setPartyVInfo("00DFDA76F7AB09B7C9"); + * kdfParams.setSuppPubInfo(null); + * kdfParams.setSuppPrivInfo(null); + * + * call.set(ConfigurationConstants.ENC_KEY_DERIVATION_PARAMS,kdfParams); + * </pre> + * + */ + public static final String ENC_KEY_DERIVATION_PARAMS = "encryptionKeyDerivationParams"; + /** * Parameter to define which parts of the request shall be encrypted. * <p/> diff --git a/ws-security-common/src/main/java/org/apache/wss4j/common/EncryptionActionToken.java b/ws-security-common/src/main/java/org/apache/wss4j/common/EncryptionActionToken.java index 472e09fbf..cf6672643 100644 --- a/ws-security-common/src/main/java/org/apache/wss4j/common/EncryptionActionToken.java +++ b/ws-security-common/src/main/java/org/apache/wss4j/common/EncryptionActionToken.java @@ -19,6 +19,8 @@ package org.apache.wss4j.common; +import org.apache.xml.security.encryption.params.KeyDerivationParameters; + /** * This class encapsulates configuration for Encryption Actions. */ @@ -28,6 +30,8 @@ public class EncryptionActionToken extends SignatureEncryptionActionToken { private String mgfAlgorithm; private String symmetricAlgorithm; private String keyAgreementMethodAlgorithm; + private String keyDerivationFunction; + private KeyDerivationParameters keyDerivationParameters; private String keyTransportAlgorithm; private boolean getSymmetricKeyFromCallbackHandler; @@ -55,6 +59,7 @@ public class EncryptionActionToken extends SignatureEncryptionActionToken { public void setKeyTransportAlgorithm(String keyTransportAlgorithm) { this.keyTransportAlgorithm = keyTransportAlgorithm; } + public String getKeyAgreementMethodAlgorithm() { return keyAgreementMethodAlgorithm; } @@ -68,5 +73,20 @@ public class EncryptionActionToken extends SignatureEncryptionActionToken { this.getSymmetricKeyFromCallbackHandler = getSymmetricKeyFromCallbackHandler; } + public String getKeyDerivationFunction() { + return keyDerivationFunction; + } + + public void setKeyDerivationFunction(String keyDerivationFunction) { + this.keyDerivationFunction = keyDerivationFunction; + } + + public KeyDerivationParameters getKeyDerivationParameters() { + return keyDerivationParameters; + } + + public void setKeyDerivationParameters(KeyDerivationParameters keyDerivationParameters) { + this.keyDerivationParameters = keyDerivationParameters; + } } diff --git a/ws-security-common/src/main/java/org/apache/wss4j/common/WSS4JConstants.java b/ws-security-common/src/main/java/org/apache/wss4j/common/WSS4JConstants.java index 9e5c80291..33fc0dd2a 100644 --- a/ws-security-common/src/main/java/org/apache/wss4j/common/WSS4JConstants.java +++ b/ws-security-common/src/main/java/org/apache/wss4j/common/WSS4JConstants.java @@ -116,10 +116,16 @@ public class WSS4JConstants { "http://www.w3.org/2001/04/xmlenc#kw-aes256"; public static final String KEYWRAP_TRIPLEDES = "http://www.w3.org/2001/04/xmlenc#kw-tripledes"; - public static final String KDF_CONCAT = + public static final String KEYDERIVATION_CONCATKDF = "http://www.w3.org/2009/xmlenc11#ConcatKDF"; + public static final String KEYDERIVATION_HKDF = + "http://www.w3.org/2021/04/xmldsig-more#hkdf"; public static final String AGREEMENT_METHOD_ECDH_ES = "http://www.w3.org/2009/xmlenc11#ECDH-ES"; + public static final String AGREEMENT_METHOD_X25519 = + "http://www.w3.org/2021/04/xmldsig-more#x25519"; + public static final String AGREEMENT_METHOD_X448 = + "http://www.w3.org/2021/04/xmldsig-more#x448"; public static final String TRIPLE_DES = "http://www.w3.org/2001/04/xmlenc#tripledes-cbc"; public static final String AES_128 = diff --git a/ws-security-common/src/main/java/org/apache/wss4j/common/crypto/AlgorithmSuiteValidator.java b/ws-security-common/src/main/java/org/apache/wss4j/common/crypto/AlgorithmSuiteValidator.java index 923ea7c37..3f707ca19 100644 --- a/ws-security-common/src/main/java/org/apache/wss4j/common/crypto/AlgorithmSuiteValidator.java +++ b/ws-security-common/src/main/java/org/apache/wss4j/common/crypto/AlgorithmSuiteValidator.java @@ -152,6 +152,26 @@ public class AlgorithmSuiteValidator { } } + /** + * Method to check the Key Derivation algorithm is on the approved list of the + * AlgorithmSuite configuration. + * @param keyDerivationFunction the key derivation function to be validated + * @throws WSSecurityException if the approved list is not empty and the key + * derivation function is not on the list + */ + public void checkKeyDerivationFunction( + String keyDerivationFunction + ) throws WSSecurityException { + Set<String> keyDerivationFunctions = algorithmSuite.getDerivedKeyAlgorithms(); + if (!keyDerivationFunctions.isEmpty() + && !keyDerivationFunctions.contains(keyDerivationFunction)) { + LOG.warn( + "The Key derivation function does not match the requirement" + ); + throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY); + } + } + public void checkSymmetricEncryptionAlgorithm( String symmetricAlgorithm ) throws WSSecurityException { diff --git a/ws-security-dom/src/main/java/org/apache/wss4j/dom/action/EncryptionAction.java b/ws-security-dom/src/main/java/org/apache/wss4j/dom/action/EncryptionAction.java index 08de7af59..7bc498714 100644 --- a/ws-security-dom/src/main/java/org/apache/wss4j/dom/action/EncryptionAction.java +++ b/ws-security-dom/src/main/java/org/apache/wss4j/dom/action/EncryptionAction.java @@ -61,12 +61,23 @@ public class EncryptionAction implements Action { if (encryptionToken.getSymmetricAlgorithm() != null) { wsEncrypt.setSymmetricEncAlgorithm(encryptionToken.getSymmetricAlgorithm()); } + if (encryptionToken.getKeyTransportAlgorithm() != null) { wsEncrypt.setKeyEncAlgo(encryptionToken.getKeyTransportAlgorithm()); } + if (encryptionToken.getKeyAgreementMethodAlgorithm() != null) { wsEncrypt.setKeyAgreementMethod(encryptionToken.getKeyAgreementMethodAlgorithm()); } + + if (encryptionToken.getKeyDerivationFunction() != null) { + wsEncrypt.setKeyDerivationMethod(encryptionToken.getKeyDerivationFunction()); + } + + if (encryptionToken.getKeyDerivationParameters() != null) { + wsEncrypt.setKeyDerivationParameters(encryptionToken.getKeyDerivationParameters()); + } + if (encryptionToken.getDigestAlgorithm() != null) { wsEncrypt.setDigestAlgorithm(encryptionToken.getDigestAlgorithm()); } diff --git a/ws-security-dom/src/main/java/org/apache/wss4j/dom/handler/WSHandler.java b/ws-security-dom/src/main/java/org/apache/wss4j/dom/handler/WSHandler.java index 7869346c7..242803038 100644 --- a/ws-security-dom/src/main/java/org/apache/wss4j/dom/handler/WSHandler.java +++ b/ws-security-dom/src/main/java/org/apache/wss4j/dom/handler/WSHandler.java @@ -47,6 +47,7 @@ import org.apache.wss4j.common.util.Loader; import org.apache.wss4j.dom.message.WSSecHeader; import org.apache.wss4j.dom.message.token.SignatureConfirmation; import org.apache.wss4j.dom.util.WSSecurityUtil; +import org.apache.xml.security.encryption.params.KeyDerivationParameters; import org.w3c.dom.Document; /** @@ -662,7 +663,12 @@ public abstract class WSHandler { String keyAgreementMethodAlgorithm = getString(WSHandlerConstants.ENC_KEY_AGREEMENT_METHOD, mc); if (keyAgreementMethodAlgorithm != null && !keyAgreementMethodAlgorithm.isEmpty()) { - algorithmSuite.addKeyAgreementMethodAlgorithm(transportAlgorithm); + algorithmSuite.addKeyAgreementMethodAlgorithm(keyAgreementMethodAlgorithm); + } + + String keyDerivationAlgorithm = getString(WSHandlerConstants.ENC_KEY_DERIVATION_FUNCTION, mc); + if (keyDerivationAlgorithm != null && !keyDerivationAlgorithm.isEmpty()) { + algorithmSuite.addDerivedKeyAlgorithm(keyDerivationAlgorithm); } reqData.setAlgorithmSuite(algorithmSuite); @@ -718,6 +724,15 @@ public abstract class WSHandler { getString(WSHandlerConstants.ENC_KEY_AGREEMENT_METHOD, mc); actionToken.setKeyAgreementMethodAlgorithm(encKeyAgreementMethod); + String encKeyDerivationAlgorithm = + getString(WSHandlerConstants.ENC_KEY_DERIVATION_FUNCTION, mc); + actionToken.setKeyDerivationFunction(encKeyDerivationAlgorithm); + + Object obj = getProperty(mc, WSHandlerConstants.ENC_KEY_DERIVATION_PARAMS); + if (obj instanceof KeyDerivationParameters) { + actionToken.setKeyDerivationParameters((KeyDerivationParameters)obj); + } + String derivedKeyReference = getString(WSHandlerConstants.DERIVED_TOKEN_REFERENCE, mc); actionToken.setDerivedKeyTokenReference(derivedKeyReference); diff --git a/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/WSSecEncryptedKey.java b/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/WSSecEncryptedKey.java index adda858c2..537c9678a 100644 --- a/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/WSSecEncryptedKey.java +++ b/ws-security-dom/src/main/java/org/apache/wss4j/dom/message/WSSecEncryptedKey.java @@ -19,28 +19,11 @@ package org.apache.wss4j.dom.message; -import java.security.*; -import java.security.cert.X509Certificate; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.SecretKey; -import javax.crypto.spec.OAEPParameterSpec; -import javax.xml.crypto.MarshalException; -import javax.xml.crypto.dom.DOMStructure; -import javax.xml.crypto.dsig.XMLSignatureFactory; -import javax.xml.crypto.dsig.keyinfo.KeyInfo; -import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory; -import javax.xml.crypto.dsig.keyinfo.KeyValue; - +import org.apache.wss4j.common.WSS4JConstants; import org.apache.wss4j.common.crypto.Crypto; import org.apache.wss4j.common.crypto.CryptoType; import org.apache.wss4j.common.ext.WSSecurityException; -import org.apache.wss4j.common.token.BinarySecurity; -import org.apache.wss4j.common.token.DOMX509Data; -import org.apache.wss4j.common.token.DOMX509IssuerSerial; -import org.apache.wss4j.common.token.Reference; -import org.apache.wss4j.common.token.SecurityTokenReference; -import org.apache.wss4j.common.token.X509Security; +import org.apache.wss4j.common.token.*; import org.apache.wss4j.common.util.AttachmentUtils; import org.apache.wss4j.common.util.KeyUtils; import org.apache.wss4j.dom.WSConstants; @@ -48,6 +31,8 @@ import org.apache.wss4j.dom.util.WSSecurityUtil; import org.apache.xml.security.encryption.XMLCipherUtil; import org.apache.xml.security.encryption.XMLEncryptionException; import org.apache.xml.security.encryption.keys.content.AgreementMethodImpl; +import org.apache.xml.security.encryption.params.ConcatKDFParams; +import org.apache.xml.security.encryption.params.HKDFParams; import org.apache.xml.security.encryption.params.KeyAgreementParameters; import org.apache.xml.security.encryption.params.KeyDerivationParameters; import org.apache.xml.security.exceptions.XMLSecurityException; @@ -58,6 +43,19 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Text; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.SecretKey; +import javax.crypto.spec.OAEPParameterSpec; +import javax.xml.crypto.MarshalException; +import javax.xml.crypto.dom.DOMStructure; +import javax.xml.crypto.dsig.XMLSignatureFactory; +import javax.xml.crypto.dsig.keyinfo.KeyInfo; +import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory; +import javax.xml.crypto.dsig.keyinfo.KeyValue; +import java.security.*; +import java.security.cert.X509Certificate; + /** * Builder class to build an EncryptedKey. * @@ -80,10 +78,23 @@ public class WSSecEncryptedKey extends WSSecBase { /** * Key agreement method algorithm used to encrypt the transport key. * Example for ECDH-ES: http://www.w3.org/2009/xmlenc11#ECDH-ES - * + * and xec example: X25519: http://www.w3.org/2021/04/xmldsig-more#x25519, + * x448: http://www.w3.org/2021/04/xmldsig-more#x448 */ private String keyAgreementMethod; + /** + * Method to derive the key to be used to encrypt the data with the Key keyAgreementMethod + * + */ + private String keyDerivationMethod = WSS4JConstants.KEYDERIVATION_HKDF; + + + /** + * The Key Derivation Parameters for the with the Key keyAgreementMethod + */ + private KeyDerivationParameters keyDerivationParameters; + /** * Digest Algorithm to be used with RSA-OAEP. The default is SHA-1 (which is not * written out unless it is explicitly configured). @@ -215,7 +226,7 @@ public class WSSecEncryptedKey extends WSSecBase { Key kek; KeyAgreementParameters dhSpec = null; - if (WSConstants.AGREEMENT_METHOD_ECDH_ES.equals(keyAgreementMethod)) { + if (isKeyAgreementConfigured(keyAgreementMethod)) { // generate ephemeral keys the key must match receivers keys dhSpec = buildKeyAgreementParameter(remoteCert.getPublicKey()); kek = generateEncryptionKey(dhSpec); @@ -373,7 +384,7 @@ public class WSSecEncryptedKey extends WSSecBase { keyInfoElement.setAttributeNS( WSConstants.XMLNS_NS, "xmlns:" + WSConstants.SIG_PREFIX, WSConstants.SIG_NS ); - if (WSConstants.AGREEMENT_METHOD_ECDH_ES.equals(keyAgreementMethod)) { + if (isKeyAgreementConfigured(keyAgreementMethod)) { try { AgreementMethodImpl agreementMethod = new AgreementMethodImpl(getDocument(), dhSpec); agreementMethod.getRecipientKeyInfo().addUnknownElement(secToken.getElement()); @@ -389,7 +400,15 @@ public class WSSecEncryptedKey extends WSSecBase { } encryptedKeyElement.appendChild(keyInfoElement); } + } + /** + * Method verifies is the key agreement method is configured(not empty) + * @param keyAgreementMethod the key agreement method + * @return true if the key agreement method is not empty else false + */ + private boolean isKeyAgreementConfigured(String keyAgreementMethod) { + return keyAgreementMethod != null && !keyAgreementMethod.isEmpty(); } private void addIssuerSerial(X509Certificate remoteCert, SecurityTokenReference secToken, boolean isCommaDelimited) @@ -534,21 +553,29 @@ public class WSSecEncryptedKey extends WSSecBase { } /** - * Method builds the KeyAgreementParameterSpec for the ECDH-ES Key Agreement Method using - * the recipient's public key and preconfigured values: keyEncAlgo, digestAlgo and keyAgreementMethod + * Method builds the KeyAgreementParameterSpec for the ECDH-ES, X25519 or X448 + * Key Agreement Method using the recipient's public key and preconfigured values: + * keyEncAlgo, digestAlgo and keyAgreementMethod. If the keyDerivationParameters + * are not set, the default values for the key derivation method are used. * * @param recipientPublicKey the recipient's public key * @return KeyAgreementParameterSpec the {@link java.security.spec.AlgorithmParameterSpec} for generating the * key for encrypting transport key and generating XML elements. * - * @throws WSSecurityException if the KeyAgreementParameterSpec cannot be created + * @throws WSSecurityException if the KeyAgreementParameterSpec cannot be created due to + * invalid key agreement method or key derivation method and wrap algorithm not supported */ private KeyAgreementParameters buildKeyAgreementParameter(PublicKey recipientPublicKey) throws WSSecurityException { KeyAgreementParameters dhSpec; try { int keyBitLength = org.apache.xml.security.utils.KeyUtils.getAESKeyBitSizeForWrapAlgorithm(keyEncAlgo); - KeyDerivationParameters kdf = XMLCipherUtil.constructConcatKeyDerivationParameter(keyBitLength, digestAlgo); + KeyDerivationParameters kdf = keyDerivationParameters; + if (keyDerivationParameters == null) { + LOG.debug("Set default KeyDerivationParameters for key derivation method: [{}]", + keyDerivationMethod); + kdf = buildDefaultKeyDerivationParameters(keyBitLength); + } KeyPair dhKeyPair = org.apache.xml.security.utils.KeyUtils.generateEphemeralDHKeyPair(recipientPublicKey, null); dhSpec = XMLCipherUtil.constructAgreementParameters(keyAgreementMethod, KeyAgreementParameters.ActorType.ORIGINATOR, kdf, null, recipientPublicKey); @@ -561,6 +588,62 @@ public class WSSecEncryptedKey extends WSSecBase { return dhSpec; } + /** + * Method builds the KeyDerivationParameters for keyDerivationMethod with default values. + * The default values for the key derivation method are: + * <ul> + * <li>ConcatKDF + * <ul> + * <li> DigestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha256"</li> + * <li> AlgorithmID: "0000"</li> + * <li> PartyUInfo: ""</li> + * <li> PartyVInfo: ""</li> + * <li> SuppPubInfo: null</li> + * <li> SuppPrivInfo: null</li> + * </ul> + * <li>HKDF: SHA-256 + * <ul> + * <li> PRF: http://www.w3.org/2001/04/xmldsig-more#hmac-sha256 </li> + * <li> Salt: random 256 bit value</li> + * <li> Info: null</li> + * </ul> + * </li> + * </ul> + * + * @param keyBitLength the length of the derived key in bits + * @return KeyDerivationParameters the {@link KeyDerivationParameters} for generating the + * key for encrypting transport key and generating XML elements. + * @throws WSSecurityException if the KeyDerivationParameters cannot be created + */ + private KeyDerivationParameters buildDefaultKeyDerivationParameters(int keyBitLength) throws WSSecurityException { + + switch (keyDerivationMethod) { + case WSS4JConstants.KEYDERIVATION_CONCATKDF: + return ConcatKDFParams.createBuilder(keyBitLength, WSConstants.SHA256) + .algorithmID("0000") + .partyUInfo("") + .partyVInfo("") + .build(); + case WSS4JConstants.KEYDERIVATION_HKDF: + // use semi random value for salt. + // rfc5869: Yet, even a salt value of less quality (shorter in + // size or with limited entropy) may still make a significant + // contribution to the security of the output keying material + byte[] semiRandom = new byte[keyBitLength / 8]; + new SecureRandom().nextBytes(semiRandom); + return HKDFParams.createBuilder(keyBitLength, WSS4JConstants.HMAC_SHA256) + .salt(semiRandom) + .info(null) + .build(); + default: + throw new WSSecurityException( + WSSecurityException.ErrorCode.FAILED_ENCRYPTION, "unsupportedKeyDerivationMethod", + new Object[]{keyDerivationMethod} + ); + } + } + + /** * Method generates the key for encrypting the transport key using the KeyAgreementParameterSpec * @@ -793,6 +876,22 @@ public class WSSecEncryptedKey extends WSSecBase { this.keyAgreementMethod = keyAgreementMethod; } + public String getKeyDerivationMethod() { + return keyDerivationMethod; + } + + public void setKeyDerivationMethod(String keyDerivationMethod) { + this.keyDerivationMethod = keyDerivationMethod; + } + + public KeyDerivationParameters getKeyDerivationParameters() { + return keyDerivationParameters; + } + + public void setKeyDerivationParameters(KeyDerivationParameters keyDerivationParameters) { + this.keyDerivationParameters = keyDerivationParameters; + } + /** * Get the id of the BSt generated during <code>prepare()</code>. * diff --git a/ws-security-dom/src/main/java/org/apache/wss4j/dom/processor/EncryptedKeyProcessor.java b/ws-security-dom/src/main/java/org/apache/wss4j/dom/processor/EncryptedKeyProcessor.java index 06622e319..e7a12412b 100644 --- a/ws-security-dom/src/main/java/org/apache/wss4j/dom/processor/EncryptedKeyProcessor.java +++ b/ws-security-dom/src/main/java/org/apache/wss4j/dom/processor/EncryptedKeyProcessor.java @@ -35,6 +35,7 @@ import javax.crypto.spec.OAEPParameterSpec; import javax.xml.crypto.dsig.XMLSignatureFactory; import org.apache.xml.security.encryption.AgreementMethod; +import org.apache.xml.security.encryption.KeyDerivationMethod; import org.apache.xml.security.encryption.XMLCipherUtil; import org.apache.xml.security.encryption.keys.RecipientKeyInfo; import org.apache.xml.security.encryption.keys.content.AgreementMethodImpl; @@ -150,9 +151,11 @@ public class EncryptedKeyProcessor implements Processor { PublicKey publicKey = null; boolean symmetricKeyWrap = isSymmetricKeyWrap(encryptedKeyTransportMethod); AgreementMethod agreementMethod = null; + KeyDerivationMethod keyDerivationMethod = null; if (isDHKeyWrap) { // get key agreement method value agreementMethod = getAgreementMethodFromElement(keyInfoChildElement); + keyDerivationMethod = getKeyDerivationFunction(agreementMethod); // get the recipient key info element keyInfoChildElement = getRecipientKeyInfoChildElement(agreementMethod); if (keyInfoChildElement == null) { @@ -184,6 +187,11 @@ public class EncryptedKeyProcessor implements Processor { agreementMethod.getAlgorithm() ); } + if (keyDerivationMethod != null) { + algorithmSuiteValidator.checkKeyDerivationFunction( + keyDerivationMethod.getAlgorithm() + ); + } } byte[] encryptedEphemeralKey = null; @@ -419,7 +427,9 @@ public class EncryptedKeyProcessor implements Processor { && WSConstants.ENC_NS.equals(keyInfoChildElement.getNamespaceURI())) { String algorithmURI = keyInfoChildElement.getAttributeNS(null, "Algorithm"); // Only ECDH_ES is supported for AgreementMethod - if (!WSConstants.AGREEMENT_METHOD_ECDH_ES.equals(algorithmURI)) { + if (!(WSConstants.AGREEMENT_METHOD_ECDH_ES.equals(algorithmURI) + || WSConstants.AGREEMENT_METHOD_X25519.equals(algorithmURI) + || WSConstants.AGREEMENT_METHOD_X448.equals(algorithmURI))) { throw new WSSecurityException( WSSecurityException.ErrorCode.UNSUPPORTED_ALGORITHM, "unknownAlgorithm", new Object[]{algorithmURI}); @@ -562,6 +572,24 @@ public class EncryptedKeyProcessor implements Processor { } } + /** + * Method retrieved the KeyDerivationMethod child element from the AgreementMethod object. + * + * @param agreementMethod object containing th KeyDerivationMethod. + * @return the {@link KeyDerivationMethod} object or null if no KeyDerivation element is specified in the agreementMethod. + * @throws WSSecurityException if KeyDerivationMethod can not be parsed from Element structure. + */ + private KeyDerivationMethod getKeyDerivationFunction(AgreementMethod agreementMethod) throws WSSecurityException { + if (agreementMethod == null) { + return null; + } + try { + return agreementMethod.getKeyDerivationMethod(); + } catch (XMLSecurityException ex) { + throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY, ex); + } + } + private X509Certificate[] getCertificatesFromX509Data( Element keyInfoChildElement, RequestData data diff --git a/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/EncryptionTest.java b/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/EncryptionTest.java index 9779d3a1b..cbcf1ada0 100644 --- a/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/EncryptionTest.java +++ b/ws-security-dom/src/test/java/org/apache/wss4j/dom/message/EncryptionTest.java @@ -23,6 +23,7 @@ import java.security.Security; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; +import java.util.UUID; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; @@ -52,7 +53,7 @@ import org.apache.wss4j.dom.handler.WSHandlerConstants; import org.apache.wss4j.dom.handler.WSHandlerResult; import org.apache.wss4j.dom.str.STRParser.REFERENCE_TYPE; import org.apache.wss4j.dom.util.WSSecurityUtil; - +import org.apache.xml.security.encryption.params.HKDFParams; import org.apache.xml.security.utils.EncryptionConstants; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -106,7 +107,7 @@ public class EncryptionTest { } @AfterEach - public void cleanup(){ + public void cleanup() { JDKTestUtils.unregisterAuxiliaryProvider(); } @@ -323,28 +324,38 @@ public class EncryptionTest { /** * Test that encrypt and decrypt a WS-Security envelope. - * This test uses the ECDSA-ES algorithm to (wrap) the symmetric key. + * This test uses the key agreement algorithm to (wrap) the symmetric key with generating KDF with + * default parameter * <p/> * + * @param algorithm The key type algorithm + * @param certAlias The certificate alias from the configuration defined in wss-ecdh.properties + * @param keyAgreementMethod The key agreement method + * @param kdfAlgorithm The key derivation method * @throws Exception Thrown when there is any problem in signing or verification */ @ParameterizedTest - @CsvSource({"xdh, X25519", - "xdh, X448", - "ec, secp256r1", - "ec, secp384r1", - "ec, secp521r1", + @CsvSource({"xdh, X25519, http://www.w3.org/2021/04/xmldsig-more#x25519, http://www.w3.org/2009/xmlenc11#ConcatKDF", + "xdh, X448, http://www.w3.org/2021/04/xmldsig-more#x448, http://www.w3.org/2009/xmlenc11#ConcatKDF", + "ec, secp256r1, http://www.w3.org/2009/xmlenc11#ECDH-ES, http://www.w3.org/2009/xmlenc11#ConcatKDF", + "ec, secp384r1, http://www.w3.org/2009/xmlenc11#ECDH-ES, http://www.w3.org/2009/xmlenc11#ConcatKDF", + "ec, secp521r1, http://www.w3.org/2009/xmlenc11#ECDH-ES, http://www.w3.org/2009/xmlenc11#ConcatKDF", + "xdh, X25519, http://www.w3.org/2021/04/xmldsig-more#x25519, http://www.w3.org/2021/04/xmldsig-more#hkdf", + "xdh, X448, http://www.w3.org/2021/04/xmldsig-more#x448, http://www.w3.org/2021/04/xmldsig-more#hkdf", + "ec, secp256r1, http://www.w3.org/2009/xmlenc11#ECDH-ES, http://www.w3.org/2021/04/xmldsig-more#hkdf", + "ec, secp384r1, http://www.w3.org/2009/xmlenc11#ECDH-ES, http://www.w3.org/2021/04/xmldsig-more#hkdf", + "ec, secp521r1, http://www.w3.org/2009/xmlenc11#ECDH-ES, http://www.w3.org/2021/04/xmldsig-more#hkdf", }) - public void testEncryptionDecryptionECDSA_ES(String algorithm, String certAlias) throws Exception { + public void testEncryptionDecryptionWithKeyAgreementAndDefaultKDF(String algorithm, String certAlias, String keyAgreementMethod, String kdfAlgorithm) throws Exception { try { if (!JDKTestUtils.isAlgorithmSupportedByJDK(algorithm)) { - LOG.info("Add AuxiliaryProvider to execute test with algorithm [{}] and cert alias [{}]", algorithm, certAlias); + LOG.info("Add AuxiliaryProvider to execute test with algorithm [{}] and cert alias [{}]", algorithm, certAlias); Security.addProvider(JDKTestUtils.getAuxiliaryProvider()); - } else if (JDKTestUtils.getJDKVersion() == 11 && algorithm.equals("xdh") ) { + } else if (JDKTestUtils.getJDKVersion() == 11 && algorithm.equals("xdh")) { // workaround for jdk11 and xdh keys // https://bugs.openjdk.java.net/browse/JDK-8219381 or https://bugs.openjdk.org/browse/JDK-8213363 // set the auxiliary provider as first provider to parse the xdh private key - Security.insertProviderAt(JDKTestUtils.getAuxiliaryProvider(), 1 ); + Security.insertProviderAt(JDKTestUtils.getAuxiliaryProvider(), 1); } Crypto encCrypto = CryptoFactory.getInstance("wss-ecdh.properties"); @@ -355,8 +366,11 @@ public class EncryptionTest { WSSecEncrypt builder = new WSSecEncrypt(secHeader); builder.setUserInfo(certAlias); builder.setKeyEncAlgo(WSConstants.KEYWRAP_AES128); - builder.setKeyAgreementMethod(WSConstants.AGREEMENT_METHOD_ECDH_ES); - builder.setDigestAlgorithm(WSS4JConstants.SHA256); + builder.setKeyAgreementMethod(keyAgreementMethod); + builder.setKeyDerivationMethod(kdfAlgorithm); + if (kdfAlgorithm.equalsIgnoreCase(WSS4JConstants.KEYDERIVATION_CONCATKDF)){ + builder.setDigestAlgorithm(WSS4JConstants.SHA256); + } builder.setKeyIdentifierType(WSConstants.SKI_KEY_IDENTIFIER); LOG.info("Before Encryption ..."); @@ -368,6 +382,7 @@ public class EncryptionTest { String outputString = XMLUtils.prettyDocumentToString(encryptedDoc); + if (LOG.isDebugEnabled()) { LOG.debug("Encrypted message:"); LOG.debug(outputString); @@ -376,7 +391,99 @@ public class EncryptionTest { // Check for algorithms and agreement method element assertTrue(outputString.contains(EncryptionConstants._TAG_AGREEMENTMETHOD)); assertTrue(outputString.contains(WSConstants.KEYWRAP_AES128)); - assertTrue(outputString.contains(WSConstants.AGREEMENT_METHOD_ECDH_ES)); + assertTrue(outputString.contains(keyAgreementMethod)); + + WSSecurityEngine newEngine = new WSSecurityEngine(); + WSHandlerResult results = + newEngine.processSecurityHeader(encryptedDoc, null, keystoreCallbackHandler, encCrypto); + + WSSecurityEngineResult actionResult = + results.getActionResults().get(WSConstants.ENCR).get(0); + assertNotNull(actionResult); + } finally { + Security.removeProvider(JDKTestUtils.getAuxiliaryProvider().getName()); + } + } + + /** + * Test that encrypt and decrypt a WS-Security envelope. + * This test uses the ECDSA-ES algorithm to (wrap) the symmetric key. + * <p/> + * + * @throws Exception Thrown when there is any problem in signing or verification + */ + @ParameterizedTest + @CsvSource({ + "xdh, X25519, http://www.w3.org/2021/04/xmldsig-more#x25519, http://www.w3.org/2001/04/xmlenc#kw-aes128, 128", + "xdh, X448, http://www.w3.org/2021/04/xmldsig-more#x448, http://www.w3.org/2001/04/xmlenc#kw-aes128, 128", + "ec, secp256r1, http://www.w3.org/2009/xmlenc11#ECDH-ES, http://www.w3.org/2001/04/xmlenc#kw-aes128, 128", + "ec, secp384r1, http://www.w3.org/2009/xmlenc11#ECDH-ES, http://www.w3.org/2001/04/xmlenc#kw-aes128, 128", + "ec, secp521r1, http://www.w3.org/2009/xmlenc11#ECDH-ES, http://www.w3.org/2001/04/xmlenc#kw-aes128, 128", + "xdh, X25519, http://www.w3.org/2021/04/xmldsig-more#x25519, http://www.w3.org/2001/04/xmlenc#kw-aes192, 192", + "xdh, X448, http://www.w3.org/2021/04/xmldsig-more#x448, http://www.w3.org/2001/04/xmlenc#kw-aes192, 192", + "ec, secp256r1, http://www.w3.org/2009/xmlenc11#ECDH-ES, http://www.w3.org/2001/04/xmlenc#kw-aes192, 192", + "ec, secp384r1, http://www.w3.org/2009/xmlenc11#ECDH-ES, http://www.w3.org/2001/04/xmlenc#kw-aes192, 192", + "ec, secp521r1, http://www.w3.org/2009/xmlenc11#ECDH-ES, http://www.w3.org/2001/04/xmlenc#kw-aes192, 192", + "xdh, X25519, http://www.w3.org/2021/04/xmldsig-more#x25519, http://www.w3.org/2001/04/xmlenc#kw-aes256, 256", + "xdh, X448, http://www.w3.org/2021/04/xmldsig-more#x448, http://www.w3.org/2001/04/xmlenc#kw-aes256, 256", + "ec, secp256r1, http://www.w3.org/2009/xmlenc11#ECDH-ES, http://www.w3.org/2001/04/xmlenc#kw-aes256, 256", + "ec, secp384r1, http://www.w3.org/2009/xmlenc11#ECDH-ES, http://www.w3.org/2001/04/xmlenc#kw-aes256, 256", + "ec, secp521r1, http://www.w3.org/2009/xmlenc11#ECDH-ES, http://www.w3.org/2001/04/xmlenc#kw-aes256, 256", + }) + public void testEncryptionDecryptionWithKeyAgreementAndHKDF(String algorithm, String certAlias, String keyAgreementMethod, String keyWrapAlg, int keySize ) throws Exception { + String hkdfMacFunction = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256"; + try { + if (!JDKTestUtils.isAlgorithmSupportedByJDK(algorithm)) { + LOG.info("Add AuxiliaryProvider to execute test with algorithm [{}] and cert alias [{}]", algorithm, certAlias); + Security.addProvider(JDKTestUtils.getAuxiliaryProvider()); + } else if (JDKTestUtils.getJDKVersion() == 11 && algorithm.equals("xdh")) { + // workaround for jdk11 and xdh keys + // https://bugs.openjdk.java.net/browse/JDK-8219381 or https://bugs.openjdk.org/browse/JDK-8213363 + // set the auxiliary provider as first provider to parse the xdh private key + Security.insertProviderAt(JDKTestUtils.getAuxiliaryProvider(), 1); + } + Crypto encCrypto = CryptoFactory.getInstance("wss-ecdh.properties"); + + Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG); + WSSecHeader secHeader = new WSSecHeader(doc); + secHeader.insertSecurityHeader(); + + HKDFParams keyDerivationParameters = HKDFParams.createBuilder(keySize, hkdfMacFunction) + .info("test-key-info".getBytes()) + .salt(UUID.randomUUID().toString().getBytes()) + .build(); + + WSSecEncrypt builder = new WSSecEncrypt(secHeader); + builder.setUserInfo(certAlias); + builder.setKeyEncAlgo(keyWrapAlg); + builder.setKeyAgreementMethod(keyAgreementMethod); + builder.setKeyDerivationParameters(keyDerivationParameters); + + + LOG.info("Before Encryption ..."); + KeyGenerator keyGen = KeyUtils.getKeyGenerator(WSConstants.AES_128_GCM); + SecretKey symmetricKey = keyGen.generateKey(); + + Document encryptedDoc = builder.build(encCrypto, symmetricKey); + LOG.info("After Encryption ...."); + + String outputString = + XMLUtils.prettyDocumentToString(encryptedDoc); + + if (LOG.isDebugEnabled()) { + LOG.debug("Encrypted message:"); + LOG.debug(outputString); + } + assertFalse(outputString.contains("counter_port_type")); + // Check for algorithms and agreement method element + assertTrue(outputString.contains(EncryptionConstants._TAG_AGREEMENTMETHOD)); + assertTrue(outputString.contains(EncryptionConstants._TAG_HKDFPARAMS)); + assertTrue(outputString.contains(EncryptionConstants._TAG_INFO)); + assertTrue(outputString.contains(EncryptionConstants._TAG_KEYLENGTH+">"+(keySize/8)+"</")); + assertTrue(outputString.contains(hkdfMacFunction)); + + assertTrue(outputString.contains(keyWrapAlg)); + assertTrue(outputString.contains(keyAgreementMethod)); WSSecurityEngine newEngine = new WSSecurityEngine(); WSHandlerResult results =
