diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index fb8d447c8..cbd67652d 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -39,6 +39,10 @@
exports org.apache.xml.security.c14n.implementations;
exports org.apache.xml.security.configuration;
exports org.apache.xml.security.encryption;
+ exports org.apache.xml.security.encryption.keys;
+ exports org.apache.xml.security.encryption.keys.content;
+ exports org.apache.xml.security.encryption.keys.content.derivedKey;
+ exports org.apache.xml.security.encryption.params;
exports org.apache.xml.security.exceptions;
exports org.apache.xml.security.keys;
exports org.apache.xml.security.keys.content;
diff --git a/src/main/java/org/apache/xml/security/algorithms/MessageDigestAlgorithm.java b/src/main/java/org/apache/xml/security/algorithms/MessageDigestAlgorithm.java
index 884a8abcf..6ff822b70 100644
--- a/src/main/java/org/apache/xml/security/algorithms/MessageDigestAlgorithm.java
+++ b/src/main/java/org/apache/xml/security/algorithms/MessageDigestAlgorithm.java
@@ -99,7 +99,7 @@ public static MessageDigestAlgorithm getInstance(
return new MessageDigestAlgorithm(doc, algorithmURI);
}
- private static MessageDigest getDigestInstance(String algorithmURI) throws XMLSignatureException {
+ public static MessageDigest getDigestInstance(String algorithmURI) throws XMLSignatureException {
String algorithmID = JCEMapper.translateURItoJCEID(algorithmURI);
if (algorithmID == null) {
diff --git a/src/main/java/org/apache/xml/security/encryption/AgreementMethod.java b/src/main/java/org/apache/xml/security/encryption/AgreementMethod.java
index 532f1a465..0339b3a2c 100644
--- a/src/main/java/org/apache/xml/security/encryption/AgreementMethod.java
+++ b/src/main/java/org/apache/xml/security/encryption/AgreementMethod.java
@@ -18,9 +18,12 @@
*/
package org.apache.xml.security.encryption;
+import java.security.PublicKey;
import java.util.Iterator;
-import org.apache.xml.security.keys.KeyInfo;
+import org.apache.xml.security.exceptions.XMLSecurityException;
+import org.apache.xml.security.encryption.keys.OriginatorKeyInfo;
+import org.apache.xml.security.encryption.keys.RecipientKeyInfo;
import org.w3c.dom.Element;
/**
@@ -88,6 +91,22 @@ public interface AgreementMethod {
*/
void setKANonce(byte[] kanonce);
+
+ /**
+ * Returns KeyDerivationMethod information used in the AgreementMethod
.
+ * @return The KeyDerivationMethod information regarding the AgreementMethod
.
+ */
+ KeyDerivationMethod getKeyDerivationMethod() throws XMLSecurityException;
+
+ /**
+ * This method is used to set the KeyDerivationMethod
when the AgreementMethod
is being
+ * used to derive a key. The KeyDerivationMethod
is declared as AgreementMethod
.
* @return additional information regarding the AgreementMethod
.
@@ -114,35 +133,42 @@ public interface AgreementMethod {
* CryptographicMethod
.
diff --git a/src/main/java/org/apache/xml/security/encryption/KeyDerivationMethod.java b/src/main/java/org/apache/xml/security/encryption/KeyDerivationMethod.java
new file mode 100644
index 000000000..b180043c5
--- /dev/null
+++ b/src/main/java/org/apache/xml/security/encryption/KeyDerivationMethod.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.xml.security.encryption;
+
+/**
+ * The key derivation is to generate new cryptographic key material from existing key material such as the shared
+ * secret and any other (private or public) information. The purpose of the key derivation is an extension of a given
+ * but limited set of original key materials and to limit the use (exposure) of such key material.
+ *
+ * The Schema for KeyDerivationMethod is as follows:
+ *
+ *+ */ +public interface KeyDerivationMethod { + + /** + * Returns the algorithm URI of this+ * + * + *+ * + *+ * + *
KeyDerivationMethod
.
+ *
+ * @return the algorithm URI of this KeyDerivationMethod
+ */
+ String getAlgorithm();
+}
diff --git a/src/main/java/org/apache/xml/security/encryption/XMLCipher.java b/src/main/java/org/apache/xml/security/encryption/XMLCipher.java
index ff7426941..75075f904 100644
--- a/src/main/java/org/apache/xml/security/encryption/XMLCipher.java
+++ b/src/main/java/org/apache/xml/security/encryption/XMLCipher.java
@@ -27,12 +27,7 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.SecureRandom;
+import java.security.*;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.MGF1ParameterSpec;
import java.util.ArrayList;
@@ -58,8 +53,11 @@
import org.apache.xml.security.algorithms.MessageDigestAlgorithm;
import org.apache.xml.security.c14n.Canonicalizer;
import org.apache.xml.security.c14n.InvalidCanonicalizerException;
+import org.apache.xml.security.encryption.keys.KeyInfoEnc;
+import org.apache.xml.security.encryption.params.KeyAgreementParameters;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.keys.KeyInfo;
+import org.apache.xml.security.encryption.keys.content.AgreementMethodImpl;
import org.apache.xml.security.keys.keyresolver.KeyResolverException;
import org.apache.xml.security.keys.keyresolver.KeyResolverSpi;
import org.apache.xml.security.keys.keyresolver.implementations.EncryptedKeyResolver;
@@ -67,10 +65,7 @@
import org.apache.xml.security.stax.ext.XMLSecurityConstants;
import org.apache.xml.security.transforms.InvalidTransformException;
import org.apache.xml.security.transforms.TransformationException;
-import org.apache.xml.security.utils.Constants;
-import org.apache.xml.security.utils.ElementProxy;
-import org.apache.xml.security.utils.EncryptionConstants;
-import org.apache.xml.security.utils.XMLUtils;
+import org.apache.xml.security.utils.*;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -148,6 +143,12 @@ public final class XMLCipher {
public static final String DIFFIE_HELLMAN =
EncryptionConstants.ALGO_ID_KEYAGREEMENT_DH;
+ /**
+ * DIFFIE_HELLMAN ES Cipher for Elliptic curve and X keys
+ */
+ public static final String DIFFIE_HELLMAN_EC =
+ EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES;
+
/** Triple DES EDE (192 bit key) in CBC mode KEYWRAP*/
public static final String TRIPLEDES_KeyWrap =
EncryptionConstants.ALGO_ID_KEYWRAP_TRIPLEDES;
@@ -1299,7 +1300,7 @@ public EncryptedKey loadEncryptedKey(Element element) throws XMLEncryptionExcept
* @throws XMLEncryptionException
*/
public EncryptedKey encryptKey(Document doc, Key key) throws XMLEncryptionException {
- return encryptKey(doc, key, null, null);
+ return encryptKey(doc, key, (String) null, null);
}
/**
@@ -1341,7 +1342,19 @@ public EncryptedKey encryptKey(
byte[] oaepParams,
SecureRandom random
) throws XMLEncryptionException {
- LOG.log(Level.DEBUG, "Encrypting key ...");
+
+ OAEPParameterSpec oaepParameters =
+ XMLCipherUtil.constructOAEPParameters(algorithm, digestAlg, mgfAlgorithm, oaepParams);
+ return encryptKey(doc, key, oaepParameters, random);
+ }
+
+ public EncryptedKey encryptKey(
+ Document doc,
+ Key key,
+ AlgorithmParameterSpec params,
+ SecureRandom random
+ ) throws XMLEncryptionException {
+ LOG.log(Level.DEBUG, "Encrypting key using algorithm specs [{0}] ...", params);
if (null == key) {
throw new XMLEncryptionException("empty", "Key unexpectedly null...");
@@ -1364,26 +1377,33 @@ public EncryptedKey encryptKey(
} else {
c = contextCipher;
}
- // Now perform the encryption
+ AlgorithmParameterSpec cipherSpec = null;
+ Key wrapKey = this.key;
+ if (params instanceof OAEPParameterSpec) {
+ cipherSpec = params;
+ } else if (params instanceof KeyAgreementParameters) {
+ KeyAgreementParameters keyAgreementParameter = (KeyAgreementParameters) params;
+ validateAndUpdateKeyAgreementParameterKeys(keyAgreementParameter);
+ // Generate a key using the key Agreement Parameters for the wrap algorithm
+ wrapKey = KeyUtils.aesWrapKeyWithDHGeneratedKey(keyAgreementParameter);
+ } else if (params != null) {
+ throw new XMLEncryptionException("encryption.UnsupportedAlgorithmParameterSpec", params.getClass().getName());
+ }
+
+ // Now perform the encryption
try {
- // Should internally generate an IV
- // todo - allow user to set an IV
- OAEPParameterSpec oaepParameters =
- constructOAEPParameters(
- algorithm, digestAlg, mgfAlgorithm, oaepParams
- );
if (random != null) {
- if (oaepParameters == null) {
- c.init(Cipher.WRAP_MODE, this.key, random);
+ if (cipherSpec == null) {
+ c.init(Cipher.WRAP_MODE, wrapKey, random);
} else {
- c.init(Cipher.WRAP_MODE, this.key, oaepParameters, random);
+ c.init(Cipher.WRAP_MODE, wrapKey, cipherSpec, random);
}
} else {
- if (oaepParameters == null) {
- c.init(Cipher.WRAP_MODE, this.key);
+ if (cipherSpec == null) {
+ c.init(Cipher.WRAP_MODE, wrapKey);
} else {
- c.init(Cipher.WRAP_MODE, this.key, oaepParameters);
+ c.init(Cipher.WRAP_MODE, wrapKey, cipherSpec);
}
}
encryptedBytes = c.wrap(key);
@@ -1401,9 +1421,24 @@ public EncryptedKey encryptKey(
try {
EncryptionMethod method = factory.newEncryptionMethod(new URI(algorithm).toString());
method.setDigestAlgorithm(digestAlg);
- method.setMGFAlgorithm(mgfAlgorithm);
- method.setOAEPparams(oaepParams);
ek.setEncryptionMethod(method);
+ if (params instanceof OAEPParameterSpec) {
+ OAEPParameterSpec oaepSpec = (OAEPParameterSpec) params;
+ String mgf1Uri = XMLCipherUtil.getMgf1URIForParameter((MGF1ParameterSpec) oaepSpec.getMGFParameters());
+ method.setMGFAlgorithm(mgf1Uri);
+ if (PSource.PSpecified.DEFAULT != oaepSpec.getPSource() && oaepSpec.getPSource() instanceof PSource.PSpecified) {
+ byte[] pSourceParams = ((PSource.PSpecified) oaepSpec.getPSource()).getValue();
+ method.setOAEPparams(pSourceParams);
+ }
+ } else if (params instanceof KeyAgreementParameters) {
+ KeyAgreementParameters keyAgreementParameter = (KeyAgreementParameters) params;
+ AgreementMethodImpl agreementMethod = new AgreementMethodImpl(contextDocument, keyAgreementParameter);
+
+ KeyInfoEnc keyInfo = new KeyInfoEnc(contextDocument);
+ keyInfo.add(agreementMethod);
+ ek.setKeyInfo(keyInfo);
+ }
+
} catch (URISyntaxException ex) {
throw new XMLEncryptionException(ex);
}
@@ -1440,7 +1475,8 @@ public Key decryptKey(EncryptedKey encryptedKey, String algorithm)
try {
String keyWrapAlg = encryptedKey.getEncryptionMethod().getAlgorithm();
String keyType = JCEMapper.getJCEKeyAlgorithmFromURI(keyWrapAlg);
- if ("RSA".equals(keyType) || "EC".equals(keyType)) {
+ if ( !(ki instanceof KeyInfoEnc && ((KeyInfoEnc)ki).containsAgreementMethod())
+ && ("RSA".equals(keyType) || "EC".equals(keyType))) {
key = ki.getPrivateKey();
} else {
key = ki.getSecretKey();
@@ -1478,17 +1514,23 @@ public Key decryptKey(EncryptedKey encryptedKey, String algorithm)
Key ret;
+ AlgorithmParameterSpec params;
try {
- EncryptionMethod encMethod = encryptedKey.getEncryptionMethod();
- OAEPParameterSpec oaepParameters =
- constructOAEPParameters(
- encMethod.getAlgorithm(), encMethod.getDigestAlgorithm(),
- encMethod.getMGFAlgorithm(), encMethod.getOAEPparams()
- );
- if (oaepParameters == null) {
+ params = getAlgorithmParameters(encryptedKey);
+ } catch (XMLSecurityException e) {
+ throw new XMLEncryptionException(e);
+ }
+
+ try {
+
+ if (params == null) {
c.init(Cipher.UNWRAP_MODE, key);
- } else {
- c.init(Cipher.UNWRAP_MODE, key, oaepParameters);
+ } else if (params instanceof OAEPParameterSpec) {
+ c.init(Cipher.UNWRAP_MODE, key, params);
+ }
+ if (params instanceof KeyAgreementParameters) {
+ Key wrapKey = KeyUtils.aesWrapKeyWithDHGeneratedKey((KeyAgreementParameters) params);
+ c.init(Cipher.UNWRAP_MODE, wrapKey);
}
ret = c.unwrap(encryptedBytes, jceKeyAlgorithm, Cipher.SECRET_KEY);
} catch (InvalidKeyException | NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
@@ -1500,42 +1542,77 @@ public Key decryptKey(EncryptedKey encryptedKey, String algorithm)
}
/**
- * Construct an OAEPParameterSpec object from the given parameters
+ * Method validates and updates if needed the KeyAgreementParameterSpec with the required keys.
+ *
+ * @param keyAgreementParameter KeyAgreementParameterSpec to be validated and updated
+ * with the required keys if needed
*/
- private OAEPParameterSpec constructOAEPParameters(
- String encryptionAlgorithm,
- String digestAlgorithm,
- String mgfAlgorithm,
- byte[] oaepParams
- ) {
- if (XMLCipher.RSA_OAEP.equals(encryptionAlgorithm)
- || XMLCipher.RSA_OAEP_11.equals(encryptionAlgorithm)) {
-
- String jceDigestAlgorithm = "SHA-1";
- if (digestAlgorithm != null) {
- jceDigestAlgorithm = JCEMapper.translateURItoJCEID(digestAlgorithm);
+ public void validateAndUpdateKeyAgreementParameterKeys(KeyAgreementParameters keyAgreementParameter) throws XMLEncryptionException {
+ if (keyAgreementParameter == null) {
+ return;
+ }
+ // check if the recipient's public key is set in keyAgreementParameter, if not, use the recipient's public key
+ // specified in XMLCipher instance init method.
+ if (keyAgreementParameter.getRecipientPublicKey() == null && this.key != null) {
+ if (this.key instanceof PublicKey) {
+ LOG.log(Level.DEBUG, "Recipient's public key is not set in keyAgreementParameter, " +
+ "use the recipient's public key specified in XMLCipher instance init method.");
+ keyAgreementParameter.setRecipientPublicKey((PublicKey) this.key);
+ } else {
+ throw new XMLEncryptionException("algorithms.WrongKeyForThisOperation",
+ this.key.getClass().getName(), "java.security.PublicKey");
}
+ }
- PSource.PSpecified pSource = PSource.PSpecified.DEFAULT;
- if (oaepParams != null) {
- pSource = new PSource.PSpecified(oaepParams);
- }
+ // check if the originator's private key is still null
+ if (keyAgreementParameter.getRecipientPublicKey() == null) {
+ // recipient's public key is mandatory for key agreement algorithm.
+ throw new XMLEncryptionException("encryption.nokey");
+ }
- MGF1ParameterSpec mgfParameterSpec = new MGF1ParameterSpec("SHA-1");
- if (XMLCipher.RSA_OAEP_11.equals(encryptionAlgorithm)) {
- if (EncryptionConstants.MGF1_SHA224.equals(mgfAlgorithm)) {
- mgfParameterSpec = new MGF1ParameterSpec("SHA-224");
- } else if (EncryptionConstants.MGF1_SHA256.equals(mgfAlgorithm)) {
- mgfParameterSpec = new MGF1ParameterSpec("SHA-256");
- } else if (EncryptionConstants.MGF1_SHA384.equals(mgfAlgorithm)) {
- mgfParameterSpec = new MGF1ParameterSpec("SHA-384");
- } else if (EncryptionConstants.MGF1_SHA512.equals(mgfAlgorithm)) {
- mgfParameterSpec = new MGF1ParameterSpec("SHA-512");
- }
- }
- return new OAEPParameterSpec(jceDigestAlgorithm, "MGF1", mgfParameterSpec, pSource);
+ if (keyAgreementParameter.getOriginatorPrivateKey() == null) {
+ LOG.log(Level.DEBUG, "Originator's private key is not set in keyAgreementParameter, " +
+ "generate an ephemeral key for the originator's private key.");
+ KeyPair originatorKeyPair = KeyUtils.generateEphemeralDHKeyPair(
+ keyAgreementParameter.getRecipientPublicKey(), null);
+ keyAgreementParameter.setOriginatorKeyPair(originatorKeyPair);
}
+ }
+ /**
+ * Method resolves the EncryptedKey using the EncryptedKeyResolver
+ *
+ * @param encryptedKey
+ * @return
+ * @throws XMLEncryptionException
+ */
+ private AlgorithmParameterSpec getAlgorithmParameters(EncryptedKey encryptedKey) throws XMLSecurityException {
+ if (encryptedKey == null
+ || encryptedKey.getEncryptionMethod() == null
+ || encryptedKey.getEncryptionMethod().getAlgorithm() == null) {
+ LOG.log(Level.DEBUG,"EncryptedKey key algorithm is null");
+ return null;
+ }
+
+ EncryptionMethod encMethod = encryptedKey.getEncryptionMethod();
+ String encryptionAlgorithm = encMethod.getAlgorithm();
+
+ if (XMLCipher.RSA_OAEP.equals(encryptionAlgorithm)
+ || XMLCipher.RSA_OAEP_11.equals(encryptionAlgorithm)) {
+ LOG.log(Level.DEBUG,"EncryptedKey key algorithm is RSA OAEP");
+ return XMLCipherUtil.constructOAEPParameters(
+ encryptionAlgorithm, encMethod.getDigestAlgorithm(),
+ encMethod.getMGFAlgorithm(), encMethod.getOAEPparams());
+ }
+
+ KeyInfoEnc keyInfo = encryptedKey.getKeyInfo() instanceof KeyInfoEnc ? (KeyInfoEnc) encryptedKey.getKeyInfo(): null;
+ if (keyInfo != null && keyInfo.containsAgreementMethod()) {
+ // resolve the agreement method
+ LOG.log(Level.DEBUG,"EncryptedKey key is using Key agreement data");
+ AgreementMethod agreementMethod = keyInfo.itemAgreementMethod(0);
+ return XMLCipherUtil.constructRecipientKeyAgreementParameters(encryptionAlgorithm,
+ agreementMethod, (PrivateKey) this.key);
+ }
return null;
}
@@ -2030,7 +2107,7 @@ private class Factory {
* @return a new AgreementMethod
*/
AgreementMethod newAgreementMethod(String algorithm) {
- return new AgreementMethodImpl(algorithm);
+ return new AgreementMethodImpl(contextDocument, algorithm);
}
/**
@@ -2338,7 +2415,7 @@ EncryptedKey newEncryptedKey(Element element) throws XMLEncryptionException {
*/
KeyInfo newKeyInfo(Element element) throws XMLEncryptionException {
try {
- KeyInfo ki = new KeyInfo(element, null);
+ KeyInfoEnc ki = new KeyInfoEnc(element, null);
ki.setSecureValidation(secureValidation);
if (internalKeyResolvers != null) {
int size = internalKeyResolvers.size();
@@ -2510,88 +2587,7 @@ Element toElement(ReferenceList referenceList) {
return ((ReferenceListImpl) referenceList).toElement();
}
- private class AgreementMethodImpl implements AgreementMethod {
- private byte[] kaNonce;
- private List+ * http://www.apache.org/licenses/LICENSE-2.0 + *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.xml.security.encryption.keys;
+
+import org.apache.xml.security.encryption.AgreementMethod;
+import org.apache.xml.security.encryption.keys.content.AgreementMethodImpl;
+import org.apache.xml.security.exceptions.XMLSecurityException;
+import org.apache.xml.security.keys.KeyInfo;
+import org.apache.xml.security.utils.ElementProxy;
+import org.apache.xml.security.utils.EncryptionConstants;
+import org.apache.xml.security.utils.I18n;
+import org.apache.xml.security.utils.XMLUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+
+/**
+ * This class is the extension of the {@link org.apache.xml.security.keys.KeyInfo} class.
+ * The {@link org.apache.xml.security.keys.KeyInfo} implements XML structures defined in XML Signature standards,
+ * and this class extends it for handling XML Element types defined by the XML encryption standards,
+ * such as AgreementMethod.
+ */
+public class KeyInfoEnc extends KeyInfo {
+
+ private static final Logger LOG = System.getLogger(KeyInfoEnc.class.getName());
+
+ /**
+ * @see KeyInfo
+ */
+ public KeyInfoEnc(Document doc) {
+ super(doc);
+ }
+
+ /**
+ * @see KeyInfo
+ */
+ public KeyInfoEnc(Element element, String baseURI) throws XMLSecurityException {
+ super(element, baseURI);
+ }
+
+ /**
+ * Method add AgreementMethod to the KeyInfo
+ *
+ * @param agreementMethod the AgreementMethod to be added. The AgreementMethod must extend
+ * class {@link ElementProxy}
+ */
+ public void add(AgreementMethod agreementMethod) {
+
+ if (agreementMethod instanceof ElementProxy) {
+ LOG.log(Level.DEBUG, "Adding agreementMethod with algorithm {0}", agreementMethod.getAlgorithm());
+ appendSelf((ElementProxy) agreementMethod);
+ addReturnToSelf();
+ } else {
+ Object[] exArgs = {EncryptionConstants._TAG_AGREEMENTMETHOD, agreementMethod.getClass().getName()};
+ throw new IllegalArgumentException(I18n.translate("KeyValue.IllegalArgument", exArgs));
+ }
+ }
+
+ /**
+ * Method lengthAgreementMethod
+ *
+ * @return the number of the AgreementMethod tags
+ */
+ public int lengthAgreementMethod() {
+ return this.length(EncryptionConstants.EncryptionSpecNS, EncryptionConstants._TAG_AGREEMENTMETHOD);
+ }
+
+ /**
+ * Method itemAgreementMethod
+ *
+ * @param i index of the AgreementMethod element
+ * @return the i(th) AgreementMethod proxy element or null if the index is too big
+ * @throws XMLSecurityException if the element with AgreementMethod exists but with wrong namespace
+ */
+ public AgreementMethod itemAgreementMethod(int i) throws XMLSecurityException {
+ Element e = XMLUtils.selectXencNode(
+ getFirstChild(), EncryptionConstants._TAG_AGREEMENTMETHOD, i);
+
+ if (e == null) {
+ LOG.log(Level.WARNING, "No AgreementMethod element at position [{0}]", i);
+ return null;
+ }
+ return new AgreementMethodImpl(e);
+ }
+
+ /**
+ * Method containsAgreementMethod returns true if the KeyInfo contains a AgreementMethod node
+ *
+ * @return true if the KeyInfo contains a AgreementMethod node else false
+ */
+ public boolean containsAgreementMethod() {
+ return this.lengthAgreementMethod() > 0;
+ }
+}
diff --git a/src/main/java/org/apache/xml/security/encryption/keys/OriginatorKeyInfo.java b/src/main/java/org/apache/xml/security/encryption/keys/OriginatorKeyInfo.java
new file mode 100644
index 000000000..73ce89bc2
--- /dev/null
+++ b/src/main/java/org/apache/xml/security/encryption/keys/OriginatorKeyInfo.java
@@ -0,0 +1,59 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.xml.security.encryption.keys;
+
+import org.apache.xml.security.exceptions.XMLSecurityException;
+import org.apache.xml.security.utils.EncryptionConstants;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+
+/**
+ * The OriginatorKeyInfo element is used in the ds:KeyInfo
with AgreementMethod elements.
+ * @see KeyAgreement. The OriginatorKeyInfo
+ * element extends ds:KeyInfo with namespace xenc (http://www.w3.org/2001/04/xmlenc#) and is used to convey
+ * the originator's public key.
+ */
+public class OriginatorKeyInfo extends KeyInfoEnc {
+
+ public OriginatorKeyInfo(Document doc) {
+ super(doc);
+ }
+
+ public OriginatorKeyInfo(Element element, String baseURI) throws XMLSecurityException {
+ super(element, baseURI);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getBaseNamespace() {
+ return EncryptionConstants.EncryptionSpecNS;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getBaseLocalName() {
+ return EncryptionConstants._TAG_ORIGINATORKEYINFO;
+ }
+}
diff --git a/src/main/java/org/apache/xml/security/encryption/keys/RecipientKeyInfo.java b/src/main/java/org/apache/xml/security/encryption/keys/RecipientKeyInfo.java
new file mode 100644
index 000000000..37bf17d1f
--- /dev/null
+++ b/src/main/java/org/apache/xml/security/encryption/keys/RecipientKeyInfo.java
@@ -0,0 +1,57 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.xml.security.encryption.keys;
+
+import org.apache.xml.security.exceptions.XMLSecurityException;
+import org.apache.xml.security.utils.EncryptionConstants;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * The RecipientKeyInfo element is used in the ds:KeyInfo
with AgreementMethod elements.
+ * @see KeyAgreement. The RecipientKeyInfo
+ * element extends ds:KeyInfo with namespace xenc (http://www.w3.org/2001/04/xmlenc#) and is used to describe
+ * the recipient's key.
+ */
+public class RecipientKeyInfo extends KeyInfoEnc {
+
+ public RecipientKeyInfo(Document doc) {
+ super(doc);
+ }
+
+ public RecipientKeyInfo(Element element, String baseURI) throws XMLSecurityException {
+ super(element, baseURI);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getBaseNamespace() {
+ return EncryptionConstants.EncryptionSpecNS;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getBaseLocalName() {
+ return EncryptionConstants._TAG_RECIPIENTKEYINFO;
+ }
+}
diff --git a/src/main/java/org/apache/xml/security/encryption/keys/content/AgreementMethodImpl.java b/src/main/java/org/apache/xml/security/encryption/keys/content/AgreementMethodImpl.java
new file mode 100644
index 000000000..30bf29eb4
--- /dev/null
+++ b/src/main/java/org/apache/xml/security/encryption/keys/content/AgreementMethodImpl.java
@@ -0,0 +1,326 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0 + *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.xml.security.encryption.keys.content;
+
+import org.apache.xml.security.encryption.AgreementMethod;
+import org.apache.xml.security.encryption.KeyDerivationMethod;
+import org.apache.xml.security.encryption.XMLEncryptionException;
+import org.apache.xml.security.encryption.params.ConcatKDFParams;
+import org.apache.xml.security.encryption.params.KeyAgreementParameters;
+import org.apache.xml.security.encryption.params.KeyDerivationParameters;
+import org.apache.xml.security.exceptions.XMLSecurityException;
+import org.apache.xml.security.encryption.keys.OriginatorKeyInfo;
+import org.apache.xml.security.encryption.keys.RecipientKeyInfo;
+import org.apache.xml.security.keys.content.KeyInfoContent;
+import org.apache.xml.security.encryption.keys.content.derivedKey.ConcatKDFParamsImpl;
+import org.apache.xml.security.encryption.keys.content.derivedKey.KeyDerivationMethodImpl;
+import org.apache.xml.security.utils.*;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.PublicKey;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+
+/**
+ * The implementation of the AgreementMethod interface. The element contains a information about
+ * the key agreement algorithm for deriving the encryption key.
+ *
+ */
+public class AgreementMethodImpl extends EncryptionElementProxy implements KeyInfoContent, AgreementMethod {
+ protected static final Logger LOG = System.getLogger(AgreementMethodImpl.class.getName());
+
+ private byte[] kaNonce;
+ private List
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.xml.security.encryption.keys.content.derivedKey;
+
+import org.apache.xml.security.algorithms.MessageDigestAlgorithm;
+import org.apache.xml.security.encryption.XMLEncryptionException;
+import org.apache.xml.security.exceptions.XMLSecurityException;
+
+import java.lang.System.Logger.Level;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Key DerivationAlgorithm implementation, defined in Section 5.8.1 of NIST SP 800-56A [SP800-56A], and is equivalent
+ * to the KDF3 function defined in ANSI X9.44-2007 [ANSI-X9-44-2007] when the contents of the OtherInfo parameter
+ * is structured as in NIST SP 800-56A.
+ *
+ * Identifier of the key derivation algorithm: http://www.w3.org/2009/xmlenc11#ConcatKDF
+ */
+public class ConcatKDF implements DerivationAlgorithm {
+
+ private static final System.Logger LOG = System.getLogger(ConcatKDF.class.getName());
+ private final String algorithmURI;
+
+ /**
+ * Constructor ConcatKDF with digest algorithmURI parameter such as http://www.w3.org/2001/04/xmlenc#sha256,
+ * http://www.w3.org/2001/04/xmlenc#sha512, etc.
+ */
+ public ConcatKDF(String algorithmURI) {
+ this.algorithmURI = algorithmURI;
+ }
+
+ /**
+ * Default Constructor which sets the default digest algorithmURI parameter: http://www.w3.org/2001/04/xmlenc#sha256,
+ */
+ public ConcatKDF() {
+ this(MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA256);
+ }
+
+ /**
+ * Key DerivationAlgorithm implementation as defined in Section 5.8.1 of NIST SP 800-56A [SP800-56A]
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.xml.security.encryption.keys.content.derivedKey;
+
+import org.apache.xml.security.exceptions.XMLSecurityException;
+import org.apache.xml.security.utils.*;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.StringJoiner;
+
+/**
+ * Class ConcatKDFParamsImpl is an DOM representation of the ConcatKDFParams.
+ */
+public class ConcatKDFParamsImpl extends Encryption11ElementProxy {
+
+
+ /**
+ * Constructor ConcatKDFParamsImpl creates a new ConcatKDFParamsImpl instance.
+ *
+ * @param doc the Document in which to create the DOM tree
+ */
+ public ConcatKDFParamsImpl(Document doc) {
+ super(doc);
+ }
+
+ /**
+ * Constructor ConcatKDFParamsImpl from existing XML element
+ * @param element the element to use as source
+ * @param baseURI the URI of the resource where the XML instance was stored
+ * @throws XMLSecurityException
+ */
+ public ConcatKDFParamsImpl(Element element, String baseURI) throws XMLSecurityException {
+ super(element, baseURI);
+ }
+
+ /**
+ * Sets the
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.xml.security.encryption.keys.content.derivedKey;
+
+import org.apache.xml.security.exceptions.XMLSecurityException;
+
+/**
+ * Interface is supported by classes to implement key derivation algorithms.
+ */
+public interface DerivationAlgorithm {
+
+ /**
+ * Derives a key from the given secret and other info. The initial derived key is size of
+ * offset + keyLength.
+ *
+ * @param secret The "shared" secret to use for key derivation (e.g. the secret key)
+ * @param otherInfo as specified in [SP800-56A] the optional attributes: AlgorithmID, PartyUInfo, PartyVInfo, SuppPubInfo and SuppPrivInfo attributes are concatenated to form a bit string “OtherInfo” that is used with the key derivation function.
+ * @param offset the starting position in derived keying material of size: offset + keyLength
+ * @param keyLength The length of the key to derive
+ * @return The derived key
+ * @throws XMLSecurityException if something goes wrong during the key derivation
+ */
+ byte[] deriveKey(byte[] secret, byte[] otherInfo, int offset,
+ long keyLength) throws XMLSecurityException;
+
+
+ /**
+ * Derives a key from the given secret and other info.
+ * @param secret The "shared" secret to use for key derivation (e.g. the secret key)
+ * @param otherInfo as specified in [SP800-56A] the optional attributes: AlgorithmID, PartyUInfo, PartyVInfo, SuppPubInfo and SuppPrivInfo attributes are concatenated to form a bit string “OtherInfo” that is used with the key derivation function.
+ * @param keyLength The length of the key to derive
+ * @return The derived key
+ * @throws XMLSecurityException if something goes wrong during the key derivation
+ */
+ default byte[] deriveKey(byte[] secret, byte[] otherInfo,
+ long keyLength) throws XMLSecurityException {
+ return deriveKey(secret, otherInfo, 0, keyLength);
+ }
+}
diff --git a/src/main/java/org/apache/xml/security/encryption/keys/content/derivedKey/KeyDerivationMethodImpl.java b/src/main/java/org/apache/xml/security/encryption/keys/content/derivedKey/KeyDerivationMethodImpl.java
new file mode 100644
index 000000000..8f59e8db8
--- /dev/null
+++ b/src/main/java/org/apache/xml/security/encryption/keys/content/derivedKey/KeyDerivationMethodImpl.java
@@ -0,0 +1,108 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.xml.security.encryption.keys.content.derivedKey;
+
+import org.apache.xml.security.encryption.KeyDerivationMethod;
+import org.apache.xml.security.exceptions.XMLSecurityException;
+import org.apache.xml.security.utils.Encryption11ElementProxy;
+import org.apache.xml.security.utils.EncryptionConstants;
+import org.apache.xml.security.utils.XMLUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.StringJoiner;
+
+/**
+ * Class KeyDerivationMethodImpl is an DOM implementation of the KeyDerivationMethod
+ *
+ */
+public class KeyDerivationMethodImpl extends Encryption11ElementProxy implements KeyDerivationMethod {
+ private ConcatKDFParamsImpl concatKDFParams;
+
+ /**
+ * Constructor KeyDerivationMethodImpl creates a new KeyDerivationMethodImpl instance.
+ *
+ * @param doc the Document in which to create the DOM tree
+ */
+ public KeyDerivationMethodImpl(Document doc) {
+ super(doc);
+ }
+
+ /**
+ * Constructor KeyDerivationMethodImpl from existing XML element
+ *
+ * @param element the element to use as source
+ * @param baseURI the URI of the resource where the XML instance was stored
+ * @throws XMLSecurityException if a parsing error occurs
+ */
+ public KeyDerivationMethodImpl(Element element, String baseURI) throws XMLSecurityException {
+ super(element, baseURI);
+ }
+
+ /**
+ * Sets the
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.xml.security.utils;
+
+import org.apache.xml.security.exceptions.DERDecodingException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.PublicKey;
+
+/**
+ * Provides the means to navigate through a DER-encoded byte array, to help
+ * in decoding the contents.
+ *
+ * It maintains a "current position" in the array that advances with each
+ * operation, providing a simple means to handle the type-length-value
+ * encoding of DER. For example
+ *
+ *
+ * Where AlgorithmIdentifier is formatted as:
+ *
+ * @param derEncodedIS the DER-encoded input stream to decode.
+ * @return the object identifier as a byte array.
+ * @throws DERDecodingException if parse error occurs.
+ */
+ public static byte[] readObjectIdentifier(InputStream derEncodedIS) throws DERDecodingException {
+ try {
+ validateType(derEncodedIS.read(), TYPE_OBJECT_IDENTIFIER);
+ int length = readLength(derEncodedIS);
+ LOG.log(System.Logger.Level.DEBUG, "DER decoding algorithm id bytes");
+ return derEncodedIS.readNBytes(length);
+ } catch (IOException ex) {
+ throw new DERDecodingException("Error occurred while reading the input stream.", ex);
+ }
+ }
+
+ /**
+ * The method extracts the algorithm OID from the public key and returns it as "dot encoded" OID string.
+ *
+ * @param publicKey the public key for which method returns algorithm ID.
+ * @return String representing the algorithm ID.
+ * @throws DERDecodingException if the algorithm ID cannot be determined.
+ */
+ public static String getAlgorithmIdFromPublicKey(PublicKey publicKey) throws DERDecodingException {
+ String keyFormat = publicKey.getFormat();
+ if (!("X.509".equalsIgnoreCase(keyFormat)
+ || "X509".equalsIgnoreCase(keyFormat))) {
+ throw new DERDecodingException("Unknown key format [" + keyFormat
+ + "]! Support for X.509-encoded public keys only!");
+ }
+ try (InputStream inputStream = new ByteArrayInputStream(publicKey.getEncoded())) {
+ byte[] keyAlgOidBytes = getAlgorithmIdBytes(inputStream);
+ String alg = decodeOID(keyAlgOidBytes);
+ if (alg.equals(KeyUtils.KeyAlgorithmType.EC.getOid())) {
+ keyAlgOidBytes = readObjectIdentifier(inputStream);
+ alg = decodeOID(keyAlgOidBytes);
+ }
+ return alg;
+ } catch (IOException ex) {
+ throw new DERDecodingException("Error reading public key", ex);
+ }
+ }
+
+ private static void validateType(int iType, byte expectedType) throws DERDecodingException {
+ validateType((byte) (iType & 0xFF), expectedType);
+ }
+
+ private static void validateType(byte type, byte expectedType) throws DERDecodingException {
+ if (type != expectedType) {
+ throw new DERDecodingException("DER decoding error: Expected type [" + expectedType + "] but got [" + type + "]");
+ }
+ }
+
+ /**
+ * Get the DER length at the current position.
+ *
+ * DER length is encoded as
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.xml.security.utils;
+
+import org.apache.xml.security.exceptions.DERDecodingException;
+import org.apache.xml.security.testutils.JDKTestUtils;
+import org.apache.xml.security.testutils.KeyTestUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assumptions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.*;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class DERDecoderUtilsTest {
+ static {
+ org.apache.xml.security.Init.init();
+ }
+
+ @AfterEach
+ void tearDown() {
+ JDKTestUtils.unregisterAuxiliaryProvider();
+ }
+
+ @ParameterizedTest
+ @EnumSource(KeyTestUtils.TestKeys.class)
+ void testGetAlgorithmIdBytesFromKey(KeyTestUtils.TestKeys testKey) throws DERDecodingException, IOException {
+
+ byte[] bytes;
+ try (InputStream keyIS = KeyTestUtils.getKeyResourceAsInputStream(testKey.getFilename());
+ InputStream keyDecodedIS = Base64.getMimeDecoder().wrap(keyIS)){
+ bytes = DERDecoderUtils.getAlgorithmIdBytes(keyDecodedIS);
+ }
+
+ String oid = DERDecoderUtils.decodeOID(bytes);
+ assertNotNull(bytes);
+ assertEquals(testKey.getOid(), oid);
+ }
+
+ @ParameterizedTest
+ @EnumSource(value = KeyUtils.KeyType.class)
+ void testGetAlgorithmIdBytesForGeneratedKeys(KeyUtils.KeyType testKey) throws DERDecodingException {
+ if (!JDKTestUtils.isAlgorithmSupportedByJDK(testKey.getAlgorithm().getJceName())) {
+ JDKTestUtils.registerAuxiliaryProvider();
+ }
+ KeyPair keyPair = KeyTestUtils.generateKeyPairIfSupported(testKey);
+ Assumptions.assumeTrue(keyPair != null, "Key algorithm [" + testKey + "] not supported by JDK or auxiliary provider! Skipping test.");
+
+ String oid = DERDecoderUtils.getAlgorithmIdFromPublicKey(keyPair.getPublic());
+ assertEquals(testKey.getOid(), oid);
+ }
+
+ @Test
+ void testKeyAlgorithmAndKeySpecificAlgorithm() throws DERDecodingException, IOException, NoSuchAlgorithmException, InvalidKeySpecException {
+ String keyBase64 = "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA/VlZpWEJjRyaFJV8abQQMxLNnaKc77MR4qFXZA3jruVRrJOzUFDD7UjcdA8FVciJY4AaEyVwdALtAM3kq4whzp4Apnp8mipZw/5VKRp3cciBr5q8A7sgPZ9qKd5RijvPsedYHIWKOjDKF0KrTx4TdnmhGR3iKqPtDSoXiRlvEYrqx9Y=";
+ byte[] publicKeyBytes = Base64.getDecoder().decode(keyBase64);
+ KeyFactory kf = KeyFactory.getInstance("EC"); // or "EC" or whatever
+ PublicKey publicKey = kf.generatePublic(new X509EncodedKeySpec(publicKeyBytes));
+ byte[] oid = DERDecoderUtils.getAlgorithmIdBytes(new ByteArrayInputStream(publicKeyBytes));
+ String keyAlgorithm = DERDecoderUtils.decodeOID(oid);
+ String keySpecificAlgorithm = DERDecoderUtils.getAlgorithmIdFromPublicKey(publicKey);
+ // expected algorithms
+ assertEquals(KeyUtils.KeyAlgorithmType.EC.getOid(), keyAlgorithm);
+ assertEquals(KeyUtils.KeyType.SECP521R1.getOid(), keySpecificAlgorithm);
+ }
+}
diff --git a/src/test/java/org/apache/xml/security/utils/KeyUtilsTest.java b/src/test/java/org/apache/xml/security/utils/KeyUtilsTest.java
new file mode 100644
index 000000000..8e426a332
--- /dev/null
+++ b/src/test/java/org/apache/xml/security/utils/KeyUtilsTest.java
@@ -0,0 +1,87 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.xml.security.utils;
+
+import org.apache.xml.security.Init;
+import org.apache.xml.security.testutils.JDKTestUtils;
+import org.apache.xml.security.testutils.KeyTestUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Assumptions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+import java.security.KeyPair;
+import java.security.Provider;
+import java.security.PublicKey;
+
+/**
+ * Unit test for {@link KeyUtils}
+ */
+class KeyUtilsTest {
+ static {
+ Init.init();
+ }
+
+ /**
+ * Test if the ephemeral key is generated with the same algorithm as the original key
+ * @param testKey the enumeration with exiting keys in resource folder
+ * @throws Exception if the key cannot be loaded
+ */
+ @ParameterizedTest
+ @EnumSource(KeyTestUtils.TestKeys.class)
+ void generateEphemeralDHKeyPair(KeyTestUtils.TestKeys testKey) throws Exception {
+ String keyAlgorithm = testKey.getAlgorithm();
+ Assumptions.assumeTrue(JDKTestUtils.isAlgorithmSupported(keyAlgorithm, true), "Algorithm ["
+ + keyAlgorithm + "] not supported by JDK or auxiliary provider! Skipping test.");
+
+ PublicKey publicKey = KeyTestUtils.loadPublicKey(testKey.getFilename(), testKey.getAlgorithm());
+ // when
+ KeyPair ephenmeralKeyPair = KeyUtils.generateEphemeralDHKeyPair(publicKey, JDKTestUtils.isAlgorithmSupportedByJDK(testKey.getAlgorithm()) ?
+ null : JDKTestUtils.getAuxiliaryProvider());
+ // then
+ Assertions.assertNotNull(ephenmeralKeyPair);
+ Assertions.assertNotEquals(publicKey, ephenmeralKeyPair.getPublic());
+ Assertions.assertEquals(publicKey.getAlgorithm(), ephenmeralKeyPair.getPublic().getAlgorithm());
+ }
+
+ /**
+ * Test if the ephemeral key is generated with the same algorithm as the original key. The initial keys are generated
+ * @param keyType the enumeration with most common EC and XEC keys
+ * @throws Exception if the key cannot be loaded
+ */
+ @ParameterizedTest
+ @EnumSource(value = KeyUtils.KeyType.class)
+ void generateEphemeralDHKeyPair(KeyUtils.KeyType keyType) throws Exception {
+
+ Provider testAuxiliaryProvider = JDKTestUtils.isAlgorithmSupportedByJDK(keyType.name()) ? null : JDKTestUtils.getAuxiliaryProvider();
+ KeyPair keyPair = KeyTestUtils.generateKeyPairIfSupported(keyType);
+ Assumptions.assumeTrue(keyPair != null, "Key algorithm [" + keyType + "] not supported by JDK or auxiliary provider! Skipping test.");
+
+
+ // test DH key generation
+ KeyPair ephenmeralKeyPair = KeyUtils.generateEphemeralDHKeyPair(keyPair.getPublic(), testAuxiliaryProvider);
+ String ephemeralKeyOId = DERDecoderUtils.getAlgorithmIdFromPublicKey(ephenmeralKeyPair.getPublic());
+
+ // test if the ephemeral key is generated with the same algorithm as the original key
+ Assertions.assertNotNull(ephenmeralKeyPair);
+ Assertions.assertNotEquals(keyPair.getPublic(), ephenmeralKeyPair.getPublic());
+ Assertions.assertEquals(keyPair.getPublic().getAlgorithm(), ephenmeralKeyPair.getPublic().getAlgorithm());
+ Assertions.assertEquals(keyType.getOid(), ephemeralKeyOId);
+ }
+}
diff --git a/src/test/resources/org/apache/xml/security/keys/content/DEREncodedKeyValue-DH.xml b/src/test/resources/org/apache/xml/security/keys/content/DEREncodedKeyValue-DH.xml
new file mode 100644
index 000000000..688d13a2d
--- /dev/null
+++ b/src/test/resources/org/apache/xml/security/keys/content/DEREncodedKeyValue-DH.xml
@@ -0,0 +1,13 @@
+AgreementMethod
will be placed
+ * @param keyAgreementParameter the {@link KeyAgreementParameters} from which AgreementMethod
will be generated
+ * @throws XMLEncryptionException if the Key derivation algorithm is not supported or invalid parameters are given.
+ */
+ public AgreementMethodImpl(Document doc, KeyAgreementParameters keyAgreementParameter) throws XMLEncryptionException {
+ this(doc, keyAgreementParameter.getKeyAgreementAlgorithm());
+
+ if (keyAgreementParameter.getKeyDerivationParameter() != null) {
+ KeyDerivationMethod keyDerivationMethod = createKeyDerivationMethod(keyAgreementParameter);
+ setKeyDerivationMethod(keyDerivationMethod);
+ }
+ // if ephemeral static key agreement then add originator public key automatically
+ if (EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES.equals(keyAgreementParameter.getKeyAgreementAlgorithm())) {
+ setOriginatorPublicKey(keyAgreementParameter.getOriginatorPublicKey());
+ }
+ // set recipient key info holder
+ RecipientKeyInfo recipientKeyInfo = new RecipientKeyInfo(getDocument());
+ setRecipientKeyInfo(recipientKeyInfo);
+ }
+
+ /**
+ * Constructor AgreementMethodImpl for generating AgreementMethod from scratch based on algorithm URI. The constructor
+ * builds a placeholder element for {@link KeyDerivationMethod}, {@link OriginatorKeyInfo} and {@link RecipientKeyInfo}. The values for these elements
+ * must be set later.
+ *
+ * @param algorithm the algorithm URI for the key agreement algorithm
+ */
+ public AgreementMethodImpl(Document doc, String algorithm) {
+ super(doc);
+
+ agreementMethodInformation = new LinkedList<>();
+ URI tmpAlgorithm;
+ try {
+ tmpAlgorithm = new URI(algorithm);
+ } catch (URISyntaxException ex) {
+ throw new IllegalArgumentException("Algorithm [" + algorithm + "] is not URI ", ex);
+ }
+ algorithmURI = tmpAlgorithm.toString();
+
+ setLocalAttribute(Constants._ATT_ALGORITHM, algorithmURI);
+ }
+
+ /**
+ * Constructor AgreementMethodImpl based on XML {@link Element}.
+ *
+ * @param element the XML {@link Element} containing AgreementMethod information
+ * @throws XMLSecurityException if the AgreementMethod element has invalid XML structure
+ */
+ public AgreementMethodImpl(Element element) throws XMLSecurityException {
+ super(element, EncryptionConstants.EncryptionSpecNS);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public byte[] getKANonce() {
+ return kaNonce;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setKANonce(byte[] kanonce) {
+ kaNonce = kanonce;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Iterator
+ *
+ *
+ * @param secret The "shared" secret to use for key derivation (e.g. the secret key)
+ * @param otherInfo as specified in [SP800-56A] the optional attributes: AlgorithmID, PartyUInfo, PartyVInfo, SuppPubInfo and SuppPrivInfo attributes are concatenated to form a bit string “OtherInfo” that is used with the key derivation function.
+ * @param offset the offset parameter is ignored by this implementation.
+ * @param keyLength The length of the key to derive
+ * @return The derived key
+ * @throws XMLEncryptionException if the key length is too long to be derived with the given algorithm
+ */
+ @Override
+ public byte[] deriveKey(byte[] secret, byte[] otherInfo, int offset, long keyLength) throws XMLSecurityException {
+
+ MessageDigest digest = MessageDigestAlgorithm.getDigestInstance(algorithmURI);
+
+ long genKeyLength = offset+keyLength;
+
+ int iDigestLength = digest.getDigestLength();
+ if (genKeyLength / iDigestLength > (long) Integer.MAX_VALUE) {
+ LOG.log(Level.ERROR, "Key size is to long to be derived with hash algorithm [{0}]", algorithmURI);
+ throw new XMLEncryptionException("errorInKeyDerivation");
+ }
+ int toGenerateSize = (int) genKeyLength;
+
+ digest.reset();
+ ByteBuffer indexBuffer = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
+
+ ByteBuffer result = ByteBuffer.allocate(toGenerateSize);
+
+ int counter = 1;
+ while (toGenerateSize > 0) {
+ indexBuffer.position(0);
+ indexBuffer.putInt(counter++);
+ indexBuffer.position(0);
+ digest.update(indexBuffer);
+ digest.update(secret);
+ if (otherInfo != null && otherInfo.length > 0) {
+ digest.update(otherInfo);
+ }
+ result.put(digest.digest(), 0, Math.min(toGenerateSize, iDigestLength));
+ toGenerateSize -= iDigestLength;
+ }
+ if (offset > 0) {
+ result.position(offset);
+ return result.slice().array();
+ }
+ return result.array();
+ }
+
+ /**
+ * Method concatenate the bitstrings in following order {@code algID || partyUInfo || partyVInfo || suppPubInfo || suppPrivInfo}.
+ * to crate otherInfo as key derivation function input.
+ * If named parameters are null the value is ignored.
+ * Method parses the bitstring firs {{@code @See} https://www.w3.org/TR/xmlenc-core1/#sec-ConcatKDF} and then concatenates them to a byte array.
+ *
+ * @param sharedSecret The "shared" secret to use for key derivation (e.g. the secret key)
+ * @param algID A bit string that indicates how the derived keying material will be parsed and for which
+ * algorithm(s) the derived secret keying material will be used.
+ * @param partyUInfo A bit string containing public information that is required by the
+ * application using this KDF to be contributed by party U to the key derivation
+ * process. At a minimum, PartyUInfo shall include IDU, the identifier of party U. See
+ * the notes below..
+ * @param partyVInfo A bit string containing public information that is required by the
+ * application using this KDF to be contributed by party V to the key derivation
+ * process. At a minimum, PartyVInfo shall include IDV, the identifier of party V. See
+ * the notes below.
+ * @param suppPubInfo bit string containing additional, mutually-known public information.
+ * @param suppPrivInfo The suppPrivInfo A bit string containing additional, mutually-known public Information.
+ * @param keyLength The length of the key to derive
+ * @return The resulting other info.
+ */
+ public byte[] deriveKey(final byte[] sharedSecret,
+ final String algID,
+ final String partyUInfo,
+ final String partyVInfo,
+ final String suppPubInfo,
+ final String suppPrivInfo,
+ final long keyLength)
+ throws XMLSecurityException {
+
+ final byte[] otherInfo = concatParameters(algID, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo);
+
+ return deriveKey(sharedSecret, otherInfo, keyLength);
+ }
+
+
+ /**
+ * Simple method to concatenate non-padded bitstream ConcatKDF parameters.
+ * If parameters are null the value is ignored.
+ *
+ * @param parameters the parameters to concatenate
+ * @return the concatenated parameters as byte array
+ */
+ private static byte[] concatParameters(final String... parameters) throws XMLEncryptionException {
+
+ List
+ *
Algorithm
attribute
+ *
+ * @param algorithm ID
+ */
+ public void setAlgorithmId(String algorithm) {
+ if (algorithm != null) {
+ setLocalAttribute(EncryptionConstants._ATT_ALGORITHM_ID, algorithm);
+ }
+ }
+
+ public String getAlgorithmId() {
+ return getLocalAttribute(EncryptionConstants._ATT_ALGORITHM_ID);
+ }
+
+ /**
+ * Sets the PartyUInfo
attribute
+ *
+ * @param partyUInfo
+ */
+ public void setPartyUInfo(String partyUInfo) {
+ if (partyUInfo != null) {
+ setLocalAttribute(EncryptionConstants._ATT_PARTYUINFO, partyUInfo);
+ }
+ }
+
+ public String getPartyUInfo() {
+ return getLocalAttribute(EncryptionConstants._ATT_PARTYUINFO);
+ }
+
+ /**
+ * Sets the PartyVInfo
attribute
+ *
+ * @param partyVInfo
+ */
+ public void setPartyVInfo(String partyVInfo) {
+ if (partyVInfo != null) {
+ setLocalAttribute(EncryptionConstants._ATT_PARTYVINFO, partyVInfo);
+ }
+ }
+
+ public String getPartyVInfo() {
+ return getLocalAttribute(EncryptionConstants._ATT_PARTYVINFO);
+ }
+
+ /**
+ * Sets the SuppPubInfo
attribute
+ *
+ * @param suppPubInfo
+ */
+ public void setSuppPubInfo(String suppPubInfo) {
+ if (suppPubInfo != null) {
+ setLocalAttribute(EncryptionConstants._ATT_SUPPPUBINFO, suppPubInfo);
+ }
+ }
+
+ public String getSuppPubInfo() {
+ return getLocalAttribute(EncryptionConstants._ATT_SUPPPUBINFO);
+ }
+
+ /**
+ * Sets the SuppPrivInfo
attribute
+ *
+ * @param suppPrivInfo
+ */
+
+ public void setSuppPrivInfo(String suppPrivInfo) {
+ if (suppPrivInfo != null) {
+ setLocalAttribute(EncryptionConstants._ATT_SUPPPRIVINFO, suppPrivInfo);
+ }
+ }
+
+ public String getSuppPrivInfo() {
+ return getLocalAttribute(EncryptionConstants._ATT_SUPPPRIVINFO);
+ }
+
+ public void setDigestMethod(String digestMethod) {
+ if (digestMethod != null) {
+ Element digestElement =
+ XMLUtils.createElementInSignatureSpace(getDocument(), Constants._TAG_DIGESTMETHOD);
+ digestElement.setAttributeNS(null, "Algorithm", digestMethod);
+ digestElement.setAttributeNS(
+ Constants.NamespaceSpecNS,
+ "xmlns:" + ElementProxy.getDefaultPrefix(Constants.SignatureSpecNS),
+ Constants.SignatureSpecNS
+ );
+ appendSelf(digestElement);
+ }
+ }
+
+ public String getDigestMethod() {
+ Element digestElement =
+ XMLUtils.selectDsNode(getElement().getFirstChild(), Constants._TAG_DIGESTMETHOD, 0);
+ if (digestElement != null) {
+ return digestElement.getAttributeNS(null, "Algorithm");
+ }
+ return null;
+ }
+
+ @Override
+ public String getBaseLocalName() {
+ return EncryptionConstants._TAG_CONCATKDFPARAMS;
+ }
+
+ @Override
+ public String toString() {
+ return new StringJoiner(", ", ConcatKDFParamsImpl.class.getSimpleName() + "[", "]")
+ .add("baseURI='" + baseURI + "'")
+ .toString();
+ }
+}
diff --git a/src/main/java/org/apache/xml/security/encryption/keys/content/derivedKey/DerivationAlgorithm.java b/src/main/java/org/apache/xml/security/encryption/keys/content/derivedKey/DerivationAlgorithm.java
new file mode 100644
index 000000000..3f24b4452
--- /dev/null
+++ b/src/main/java/org/apache/xml/security/encryption/keys/content/derivedKey/DerivationAlgorithm.java
@@ -0,0 +1,55 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * Algorithm
attribute
+ *
+ * @param algorithm ID
+ */
+ public void setAlgorithm(String algorithm) {
+ if (algorithm != null) {
+ setLocalIdAttribute(EncryptionConstants._ATT_ALGORITHM, algorithm);
+ }
+ }
+
+ @Override
+ public String getAlgorithm() {
+ return getLocalAttribute(EncryptionConstants._ATT_ALGORITHM);
+ }
+
+ public ConcatKDFParamsImpl getConcatKDFParams() throws XMLSecurityException {
+
+ if (concatKDFParams != null) {
+ return concatKDFParams;
+ }
+
+ Element concatKDFParamsElement =
+ XMLUtils.selectXenc11Node(getElement().getFirstChild(), EncryptionConstants._TAG_CONCATKDFPARAMS, 0);
+
+ if (concatKDFParamsElement == null) {
+ return null;
+ }
+ concatKDFParams = new ConcatKDFParamsImpl(concatKDFParamsElement, getBaseURI());
+
+ return concatKDFParams;
+ }
+
+ public void setConcatKDFParams(ConcatKDFParamsImpl concatKDFParams) {
+ this.concatKDFParams = concatKDFParams;
+ appendSelf(concatKDFParams);
+ addReturnToSelf();
+ }
+
+ @Override
+ public String getBaseLocalName() {
+ return EncryptionConstants._TAG_KEYDERIVATIONMETHOD;
+ }
+
+ @Override
+ public String toString() {
+ return new StringJoiner(", ", KeyDerivationMethodImpl.class.getSimpleName() + "[", "]")
+ .add("concatKDFParams=" + concatKDFParams)
+ .toString();
+ }
+}
diff --git a/src/main/java/org/apache/xml/security/encryption/params/ConcatKDFParams.java b/src/main/java/org/apache/xml/security/encryption/params/ConcatKDFParams.java
new file mode 100644
index 000000000..891c5ff57
--- /dev/null
+++ b/src/main/java/org/apache/xml/security/encryption/params/ConcatKDFParams.java
@@ -0,0 +1,102 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.xml.security.encryption.params;
+
+import org.apache.xml.security.algorithms.MessageDigestAlgorithm;
+import org.apache.xml.security.utils.EncryptionConstants;
+
+/**
+ * Class ConcatKeyDerivationParameter is used to specify parameters for the ConcatKDF key derivation algorithm.
+ * @see XML Encryption Syntax and Processing Version 1.1, 5.8.1
+ * The ConcatKDF Key Derivation Algorithm
+ */
+public class ConcatKDFParams extends KeyDerivationParameters {
+
+ private String digestAlgorithm;
+ private String algorithmID;
+ private String partyUInfo;
+ private String partyVInfo;
+ private String suppPubInfo;
+ private String suppPrivInfo;
+
+ /**
+ * Constructor ConcatKeyDerivationParameter with specified digest algorithm
+ *
+ * @param keyBitLength the length of the derived key in bits
+ * @param digestAlgorithm the digest algorithm to use
+ */
+ public ConcatKDFParams(int keyBitLength, String digestAlgorithm) {
+ super(EncryptionConstants.ALGO_ID_KEYDERIVATION_CONCATKDF, keyBitLength);
+ this.digestAlgorithm = digestAlgorithm;
+ }
+
+ /**
+ * Method return the digest algorithm. In case of algorithm is not set, the "default"
+ * algorithm SHA256 digest algorithm is returned.
+ *
+ * @return the digest algorithm
+ */
+ public String getDigestAlgorithm() {
+ return digestAlgorithm == null ? MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA256 : digestAlgorithm;
+ }
+
+ public void setDigestAlgorithm(String digestAlgorithm) {
+ this.digestAlgorithm = digestAlgorithm;
+ }
+
+ public String getAlgorithmID() {
+ return algorithmID;
+ }
+
+ public void setAlgorithmID(String algorithmID) {
+ this.algorithmID = algorithmID;
+ }
+
+ public String getPartyUInfo() {
+ return partyUInfo;
+ }
+
+ public void setPartyUInfo(String partyUInfo) {
+ this.partyUInfo = partyUInfo;
+ }
+
+ public String getPartyVInfo() {
+ return partyVInfo;
+ }
+
+ public void setPartyVInfo(String partyVInfo) {
+ this.partyVInfo = partyVInfo;
+ }
+
+ public String getSuppPubInfo() {
+ return suppPubInfo;
+ }
+
+ public void setSuppPubInfo(String suppPubInfo) {
+ this.suppPubInfo = suppPubInfo;
+ }
+
+ public String getSuppPrivInfo() {
+ return suppPrivInfo;
+ }
+
+ public void setSuppPrivInfo(String suppPrivInfo) {
+ this.suppPrivInfo = suppPrivInfo;
+ }
+}
diff --git a/src/main/java/org/apache/xml/security/encryption/params/KeyAgreementParameters.java b/src/main/java/org/apache/xml/security/encryption/params/KeyAgreementParameters.java
new file mode 100644
index 000000000..70e9f4ac3
--- /dev/null
+++ b/src/main/java/org/apache/xml/security/encryption/params/KeyAgreementParameters.java
@@ -0,0 +1,126 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.xml.security.encryption.params;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * This class is used to pass parameters to the KeyAgreement algorithm.
+ */
+public class KeyAgreementParameters implements AlgorithmParameterSpec {
+ /**
+ * This enum defines the actor type of the KeyAgreement algorithm. The actor type defines which public and which
+ * private key is expected to be present for the KeyAgreement algorithm to define derived key.
+ *
+ *
+ */
+ public enum ActorType {
+ ORIGINATOR,
+ RECIPIENT
+ }
+
+ private final KeyDerivationParameters KeyDerivationParameter;
+ private final ActorType actorType;
+ private final String keyAgreementAlgorithm;
+
+ private PublicKey originatorPublicKey;
+ private PrivateKey originatorPrivateKey;
+ private PublicKey recipientPublicKey;
+ private PrivateKey recipientPrivateKey;
+
+
+ public KeyAgreementParameters(ActorType actorType, String keyAgreementAlgorithm, KeyDerivationParameters keyDerivationParameter) {
+ this.actorType = actorType;
+ this.KeyDerivationParameter = keyDerivationParameter;
+ this.keyAgreementAlgorithm = keyAgreementAlgorithm;
+ }
+
+ public KeyDerivationParameters getKeyDerivationParameter() {
+ return KeyDerivationParameter;
+ }
+
+ public String getKeyAgreementAlgorithm() {
+ return keyAgreementAlgorithm;
+ }
+
+ public void setOriginatorKeyPair(KeyPair originatorKeyPair) {
+ this.originatorPublicKey = originatorKeyPair.getPublic();
+ this.originatorPrivateKey = originatorKeyPair.getPrivate();
+ }
+
+ public PublicKey getOriginatorPublicKey() {
+ return originatorPublicKey;
+ }
+
+ public void setOriginatorPublicKey(PublicKey originatorPublicKey) {
+ this.originatorPublicKey = originatorPublicKey;
+ }
+
+ public PrivateKey getOriginatorPrivateKey() {
+ return originatorPrivateKey;
+ }
+
+ public void setOriginatorPrivateKey(PrivateKey originatorPrivateKey) {
+ if (actorType != ActorType.ORIGINATOR) {
+ throw new IllegalStateException("Cannot set originator private key when actor type is not ORIGINATOR");
+ }
+ this.originatorPrivateKey = originatorPrivateKey;
+ }
+
+ public PublicKey getRecipientPublicKey() {
+ return recipientPublicKey;
+ }
+
+ public void setRecipientPublicKey(PublicKey recipientPublicKey) {
+ if (actorType != ActorType.ORIGINATOR) {
+ throw new IllegalStateException("Cannot set recipient public key when actor type is not ORIGINATOR");
+ }
+ this.recipientPublicKey = recipientPublicKey;
+ }
+
+ public PrivateKey getRecipientPrivateKey() {
+ return recipientPrivateKey;
+ }
+
+ public void setRecipientPrivateKey(PrivateKey recipientPrivateKey) {
+ if (actorType != ActorType.RECIPIENT) {
+ throw new IllegalStateException("Cannot set recipient private key when actor type is not RECIPIENT");
+ }
+ this.recipientPrivateKey = recipientPrivateKey;
+ }
+
+ public ActorType getActorType() {
+ return actorType;
+ }
+
+ public PublicKey getAgreementPublicKey() {
+ return actorType == ActorType.ORIGINATOR ? recipientPublicKey : originatorPublicKey;
+ }
+
+ public PrivateKey getAgreementPrivateKey() {
+ return actorType == ActorType.ORIGINATOR ? originatorPrivateKey : recipientPrivateKey;
+ }
+}
diff --git a/src/main/java/org/apache/xml/security/encryption/params/KeyDerivationParameters.java b/src/main/java/org/apache/xml/security/encryption/params/KeyDerivationParameters.java
new file mode 100644
index 000000000..e22dbff82
--- /dev/null
+++ b/src/main/java/org/apache/xml/security/encryption/params/KeyDerivationParameters.java
@@ -0,0 +1,41 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.xml.security.encryption.params;
+
+/**
+ * Abstract key derivation class contains the basic parameters used for the key derivation.
+ * The class should be extended to provide algorithm specific parameters.
+ */
+public abstract class KeyDerivationParameters {
+ private final String algorithm;
+ private final int keyBitLength;
+
+ public KeyDerivationParameters(String algorithm, int keyLength) {
+ this.algorithm = algorithm;
+ this.keyBitLength = keyLength;
+ }
+
+ public String getAlgorithm() {
+ return algorithm;
+ }
+
+ public int getKeyBitLength() {
+ return keyBitLength;
+ }
+}
diff --git a/src/main/java/org/apache/xml/security/exceptions/DERDecodingException.java b/src/main/java/org/apache/xml/security/exceptions/DERDecodingException.java
new file mode 100644
index 000000000..1054664dd
--- /dev/null
+++ b/src/main/java/org/apache/xml/security/exceptions/DERDecodingException.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.xml.security.exceptions;
+
+/**
+ * This Exception is thrown if decoding of ASN.1 (DER) data fails.
+ *
+ */
+public class DERDecodingException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor DERDecodingException
+ *
+ * @param message the message to display when this exception is thrown
+ */
+ public DERDecodingException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructor DERDecodingException
+ *
+ * @param message the message to display when this exception is thrown
+ * @param cause the cause of this exception
+ */
+ public DERDecodingException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/org/apache/xml/security/keys/KeyInfo.java b/src/main/java/org/apache/xml/security/keys/KeyInfo.java
index f30db6ea1..d1e90dec1 100644
--- a/src/main/java/org/apache/xml/security/keys/KeyInfo.java
+++ b/src/main/java/org/apache/xml/security/keys/KeyInfo.java
@@ -33,15 +33,7 @@
import org.apache.xml.security.encryption.XMLCipher;
import org.apache.xml.security.encryption.XMLEncryptionException;
import org.apache.xml.security.exceptions.XMLSecurityException;
-import org.apache.xml.security.keys.content.DEREncodedKeyValue;
-import org.apache.xml.security.keys.content.KeyInfoReference;
-import org.apache.xml.security.keys.content.KeyName;
-import org.apache.xml.security.keys.content.KeyValue;
-import org.apache.xml.security.keys.content.MgmtData;
-import org.apache.xml.security.keys.content.PGPData;
-import org.apache.xml.security.keys.content.RetrievalMethod;
-import org.apache.xml.security.keys.content.SPKIData;
-import org.apache.xml.security.keys.content.X509Data;
+import org.apache.xml.security.keys.content.*;
import org.apache.xml.security.keys.content.keyvalues.DSAKeyValue;
import org.apache.xml.security.keys.content.keyvalues.RSAKeyValue;
import org.apache.xml.security.keys.keyresolver.KeyResolver;
@@ -49,11 +41,7 @@
import org.apache.xml.security.keys.keyresolver.KeyResolverSpi;
import org.apache.xml.security.keys.storage.StorageResolver;
import org.apache.xml.security.transforms.Transforms;
-import org.apache.xml.security.utils.Constants;
-import org.apache.xml.security.utils.ElementProxy;
-import org.apache.xml.security.utils.EncryptionConstants;
-import org.apache.xml.security.utils.SignatureElementProxy;
-import org.apache.xml.security.utils.XMLUtils;
+import org.apache.xml.security.utils.*;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -90,7 +78,7 @@
* contains the corresponding type.
*
*/
-public class KeyInfo extends SignatureElementProxy {
+public class KeyInfo extends ElementProxy {
private static final Logger LOG = System.getLogger(KeyInfo.class.getName());
@@ -233,12 +221,24 @@ public void add(RSAKeyValue rsakeyvalue) {
}
/**
- * Method add
+ * Method adds public key encoded as KeyValue. If public key type is not supported by KeyValue, then
+ * DEREncodedKeyValue is used. If public key type is not supported by DEREncodedKeyValue, then
+ * IllegalArgumentException is thrown.
*
- * @param pk
+ * @param pk public key to be added to KeyInfo
*/
- public void add(PublicKey pk) {
- this.add(new KeyValue(getDocument(), pk));
+ public void add(PublicKey pk) {
+
+ if (KeyValue.isSupportedKeyType(pk)) {
+ this.add(new KeyValue(getDocument(), pk));
+ return;
+ }
+
+ try {
+ this.add(new DEREncodedKeyValue(getDocument(), pk));
+ } catch (XMLSecurityException ex) {
+ throw new IllegalArgumentException(ex);
+ }
}
/**
@@ -813,6 +813,7 @@ public boolean containsKeyInfoReference() {
return this.lengthKeyInfoReference() > 0;
}
+
/**
* This method returns the public key.
*
@@ -1229,4 +1230,10 @@ public void addStorageResolver(StorageResolver storageResolver) {
public String getBaseLocalName() {
return Constants._TAG_KEYINFO;
}
+
+ /** {@inheritDoc} */
+ @Override
+ public String getBaseNamespace() {
+ return Constants.SignatureSpecNS;
+ }
}
diff --git a/src/main/java/org/apache/xml/security/keys/content/DEREncodedKeyValue.java b/src/main/java/org/apache/xml/security/keys/content/DEREncodedKeyValue.java
index f1e8faf06..a5c402578 100644
--- a/src/main/java/org/apache/xml/security/keys/content/DEREncodedKeyValue.java
+++ b/src/main/java/org/apache/xml/security/keys/content/DEREncodedKeyValue.java
@@ -37,7 +37,10 @@
public class DEREncodedKeyValue extends Signature11ElementProxy implements KeyInfoContent {
/** JCA algorithm key types supported by this implementation. */
- private static final String[] supportedKeyTypes = { "RSA", "DSA", "EC"};
+ private static final String[] supportedKeyTypes = { "RSA", "DSA", "EC",
+ "DiffieHellman", "DH", "XDH", "X25519", "X448",
+ "EdDSA", "Ed25519", "Ed448",
+ "RSASSA-PSS"};
/**
* Constructor DEREncodedKeyValue
@@ -140,5 +143,4 @@ protected byte[] getEncodedDER(PublicKey publicKey) throws XMLSecurityException
throw new XMLSecurityException(e, "DEREncodedKeyValue.UnsupportedPublicKey", exArgs);
}
}
-
}
diff --git a/src/main/java/org/apache/xml/security/keys/content/KeyValue.java b/src/main/java/org/apache/xml/security/keys/content/KeyValue.java
index ab8b79b99..98d741875 100644
--- a/src/main/java/org/apache/xml/security/keys/content/KeyValue.java
+++ b/src/main/java/org/apache/xml/security/keys/content/KeyValue.java
@@ -37,7 +37,6 @@
* (section 6.4). The KeyValue element may include externally defined public
* keys values represented as PCDATA or element types from an external
* namespace.
- *
*/
public class KeyValue extends SignatureElementProxy implements KeyInfoContent {
@@ -116,6 +115,20 @@ public KeyValue(Document doc, PublicKey pk) {
}
}
+ /**
+ * Verifies that the XML KeyValue encoding is supported for the given key type. If the
+ * encoding is supported, it returns true else false.
+ *
+ * @return true if the public key has a KeyValue encoding, false otherwise.
+ */
+ public static boolean isSupportedKeyType(PublicKey publicKey) {
+
+ return publicKey instanceof java.security.interfaces.DSAPublicKey
+ || publicKey instanceof java.security.interfaces.RSAPublicKey
+ || publicKey instanceof java.security.interfaces.ECPublicKey;
+
+ }
+
/**
* Constructor KeyValue
*
diff --git a/src/main/java/org/apache/xml/security/resource/xmlsecurity_de.properties b/src/main/java/org/apache/xml/security/resource/xmlsecurity_de.properties
index 03a236956..68d2aced1 100644
--- a/src/main/java/org/apache/xml/security/resource/xmlsecurity_de.properties
+++ b/src/main/java/org/apache/xml/security/resource/xmlsecurity_de.properties
@@ -66,6 +66,7 @@ encryption.RSAPKCS15.blockTruncated = Block abgeschnitten
encryption.RSAPKCS15.noDataInBlock = Im Block sind keine Daten enthalten
encryption.RSAPKCS15.unknownBlockType = Unbekannter Block Typ
encryption.nokey = Es ist kein verschl\u00fcsselungs Schl\u00fcssel geladen und es konnte kein Schl\u00fcssel mit Hilfe der "key resolvers" gefunden werden.
+encryption.UnsupportedAlgorithmParameterSpec=Nicht unterst\u00fctzter Algorithmusparametertyp: {0}
endorsed.jdk1.4.0 = Leider scheint niemand unsere Installations-Anleitung zu lesen, deshalb m\u00fcssen wir es \u00fcber die Exception machen\: Du hast den "endorsing" Mechanismus vom JDK 1.4 nicht richtig angewandt. Schaue unter
+ * decoder.expect(TYPE);
+ * int length = decoder.getLength();
+ * byte[] value = decoder.getBytes(len);
+ *
+ */
+public class DERDecoderUtils {
+ private static final System.Logger LOG = System.getLogger(DERDecoderUtils.class.getName());
+
+ /**
+ * DER type identifier for a bit string value
+ */
+ public static final byte TYPE_BIT_STRING = 0x03;
+ /**
+ * DER type identifier for a octet string value
+ */
+ public static final byte TYPE_OCTET_STRING = 0x04;
+ /**
+ * DER type identifier for a sequence value
+ */
+ public static final byte TYPE_SEQUENCE = 0x30;
+ /**
+ * DER type identifier for ASN.1 "OBJECT IDENTIFIER" value.
+ */
+ public static final byte TYPE_OBJECT_IDENTIFIER = 0x06;
+
+ /**
+ * Simple method parses an ASN.1 encoded byte array. The encoding uses "DER", a BER/1 subset, that means a triple { typeId, length, data }.
+ * with the following structure:
+ *
+ * PublicKeyInfo ::= SEQUENCE {
+ * algorithm AlgorithmIdentifier,
+ * PublicKey BIT STRING
+ * }
+ *
+ *
+ * AlgorithmIdentifier ::= SEQUENCE {
+ * algorithm OBJECT IDENTIFIER,
+ * parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ *
+ * @param derEncodedIS the DER-encoded input stream to decode.
+ * @throws DERDecodingException in case of decoding error or if given InputStream is null or empty.
+ * @throws IOException if an I/O error occurs.
+ */
+ public static byte[] getAlgorithmIdBytes(InputStream derEncodedIS) throws DERDecodingException, IOException {
+ if (derEncodedIS == null || derEncodedIS.available() <= 0) {
+ throw new DERDecodingException("DER decoding error: Null data");
+ }
+
+ validateType(derEncodedIS.read(), TYPE_SEQUENCE);
+ readLength(derEncodedIS);
+ validateType(derEncodedIS.read(), TYPE_SEQUENCE);
+ readLength(derEncodedIS);
+
+ return readObjectIdentifier(derEncodedIS);
+ }
+
+ /**
+ * Read the next object identifier from the given DER-encoded input stream.
+ *
+ *
+ *
+ * @return the length, -1 for indefinite length.
+ * @throws DERDecodingException if the current position is at the end of the array or there is
+ * an incomplete length specification.
+ * @throws IOException if an I/O error occurs.
+ */
+ public static int readLength(InputStream derEncodedIs) throws DERDecodingException, IOException {
+ if (derEncodedIs.available() <= 0) {
+ throw new DERDecodingException("Invalid DER format");
+ }
+
+ int value = derEncodedIs.read();
+
+ if ((value & 0x080) == 0x00) { // short form, 1 byte size
+ return value;
+ }
+ // number of bytes used to encode length
+ int byteCount = value & 0x07f;
+ //byteCount == 0 indicates indefinite length encoded data.
+ if (byteCount == 0) {
+ return -1;
+ }
+
+ // byteCount > 4 not able to handle more than 4Gb of data (max int size) tmp > 4 indicates.
+ if (byteCount > 4) {
+ throw new DERDecodingException("Data length byte size: [" + byteCount + "] is incorrect/too big");
+ }
+ byte[] intSizeBytes = derEncodedIs.readNBytes(byteCount);
+ return new BigInteger(1, intSizeBytes).intValue();
+ }
+
+
+ /**
+ * The first two nodes of the OID are encoded onto a single byte.
+ * The first node is multiplied by the decimal 40 and the result is added to the value of the second node.
+ * Node values less than or equal to 127 are encoded in one byte.
+ * Node values greater than or equal to 128 are encoded on multiple bytes.
+ * Bit 7 of the leftmost byte is set to one. Bits 0 through 6 of each byte contains the encoded value.
+ *
+ * @param oidBytes the byte array containing the OID
+ * @return the decoded OID as a string
+ */
+ public static String decodeOID(byte[] oidBytes) {
+
+ int length = oidBytes.length;
+ StringBuilder sb = new StringBuilder(length * 4);
+
+ int fromPos = 0;
+ for (int i = 0; i < length; i++) {
+ // if the 8th bit is set, it means the next byte is part of the current segment
+ if ((oidBytes[i] & 0x80) != 0) {
+ continue;
+ }
+
+ // decode the OID segment
+ long decodedValue = decodeBytes(oidBytes, fromPos, i - fromPos + 1);
+ if (fromPos == 0) {
+ // first OID segment consists of two numbers
+ if (decodedValue < 80) {
+ sb.append(decodedValue / 40);
+ decodedValue = decodedValue % 40;
+ } else {
+ sb.append('2');
+ decodedValue = decodedValue - 80;
+ }
+ }
+
+ //add next OID segment
+ sb.append('.');
+ sb.append(decodedValue);
+ fromPos = i + 1;
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Decode a byte array into a long value. The most significant bit of each byte is ignored because it
+ * is used as a continuation flag. Bits are shifted to the left so that the most significant 7 bits are in first byte
+ * next 7 bits in second byte and so on.
+ *
+ * @param inBytes the input byte array
+ * @param iOffset start point inside inBytes
+ * @param iLength number of bytes to decode
+ * @return long value decoded from the byte array.
+ */
+ private static long decodeBytes(byte[] inBytes, int iOffset, int iLength) {
+ // check if the OID segment is too big to decode with long value!
+ if (iLength > 8) {
+ throw new IllegalArgumentException("OID segment too long to parse: ["+iLength+"]");
+ }
+ if (iLength > 1) {
+ int iSteps = iLength - 1;
+ return ((long) (inBytes[iOffset] & 0x07f) << 7 * iSteps)
+ + decodeBytes(inBytes, iOffset + 1, iSteps);
+ } else {
+ return inBytes[iOffset] & 0x07f;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/xml/security/utils/Encryption11ElementProxy.java b/src/main/java/org/apache/xml/security/utils/Encryption11ElementProxy.java
new file mode 100644
index 000000000..ba9f6e394
--- /dev/null
+++ b/src/main/java/org/apache/xml/security/utils/Encryption11ElementProxy.java
@@ -0,0 +1,67 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.xml.security.utils;
+
+import org.apache.xml.security.exceptions.XMLSecurityException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Class Encryption11ElementProxy
+ */
+public abstract class Encryption11ElementProxy extends ElementProxy {
+
+ protected Encryption11ElementProxy() {
+ }
+
+ /**
+ * Constructor Encryption11ElementProxy
+ *
+ * @param doc
+ */
+ public Encryption11ElementProxy(Document doc) {
+ if (doc == null) {
+ throw new RuntimeException("Document is null");
+ }
+ setDocument(doc);
+ setElement(XMLUtils.createElementInEncryption11Space(doc, this.getBaseLocalName()));
+ String prefix = ElementProxy.getDefaultPrefix(this.getBaseNamespace());
+ if (prefix != null && prefix.length() > 0) {
+ getElement().setAttribute("xmlns:" + prefix, this.getBaseNamespace());
+ }
+ }
+
+ /**
+ * Constructor Signature11ElementProxy
+ *
+ * @param element
+ * @param baseURI
+ * @throws XMLSecurityException
+ */
+ public Encryption11ElementProxy(Element element, String baseURI) throws XMLSecurityException {
+ super(element, baseURI);
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getBaseNamespace() {
+ return EncryptionConstants.EncryptionSpec11NS;
+ }
+}
diff --git a/src/main/java/org/apache/xml/security/utils/EncryptionConstants.java b/src/main/java/org/apache/xml/security/utils/EncryptionConstants.java
index 48381e9d0..284e43a65 100644
--- a/src/main/java/org/apache/xml/security/utils/EncryptionConstants.java
+++ b/src/main/java/org/apache/xml/security/utils/EncryptionConstants.java
@@ -45,6 +45,22 @@ public final class EncryptionConstants {
/** Tag of Attr mimetype **/
public static final String _ATT_MIMETYPE = "MimeType";
+ /** Tag of Attr AlgorithmID**/
+ public static final String _ATT_ALGORITHM_ID = "AlgorithmID";
+
+ /** Tag of Attr PartyUInfo**/
+ public static final String _ATT_PARTYUINFO = "PartyUInfo";
+
+ /** Tag of Attr PartyVInfo**/
+ public static final String _ATT_PARTYVINFO = "PartyVInfo";
+
+ /** Tag of Attr PartyVInfo**/
+ public static final String _ATT_SUPPPUBINFO = "SuppPubInfo";
+
+ /** Tag of Attr PartyVInfo**/
+ public static final String _ATT_SUPPPRIVINFO = "SuppPrivInfo";
+
+
/** Tag of Element CarriedKeyName **/
public static final String _TAG_CARRIEDKEYNAME = "CarriedKeyName";
@@ -105,6 +121,12 @@ public final class EncryptionConstants {
/** Tag of Element RecipientKeyInfo **/
public static final String _TAG_RECIPIENTKEYINFO = "RecipientKeyInfo";
+ /** Tag of Element KeyDerivationMethod **/
+ public static final String _TAG_KEYDERIVATIONMETHOD = "KeyDerivationMethod";
+
+ /** Tag of Element ConcatKDFParams **/
+ public static final String _TAG_CONCATKDFPARAMS = "ConcatKDFParams";
+
/** Field ENCRYPTIONSPECIFICATION_URL */
public static final String ENCRYPTIONSPECIFICATION_URL =
"http://www.w3.org/TR/2001/WD-xmlenc-core-20010626/";
@@ -191,6 +213,10 @@ public final class EncryptionConstants {
public static final String ALGO_ID_KEYAGREEMENT_DH =
EncryptionConstants.EncryptionSpecNS + "dh";
+ /** Key Agreement Diffie-Hellman for EC (and X) keys with the originator ephemeral and receiver static key */
+ public static final String ALGO_ID_KEYAGREEMENT_ECDH_ES =
+ EncryptionConstants.EncryptionSpec11NS + "ECDH-ES";
+
/** Symmetric Key Wrap - REQUIRED TRIPLEDES KeyWrap */
public static final String ALGO_ID_KEYWRAP_TRIPLEDES =
EncryptionConstants.EncryptionSpecNS + "kw-tripledes";
@@ -259,6 +285,9 @@ public final class EncryptionConstants {
public static final String MGF1_SHA512 =
EncryptionConstants.EncryptionSpec11NS + "mgf1sha512";
+ /** Key derivation function ConcatKDF */
+ public static final String ALGO_ID_KEYDERIVATION_CONCATKDF =
+ EncryptionConstants.EncryptionSpec11NS + "ConcatKDF";
private EncryptionConstants() {
// we don't allow instantiation
diff --git a/src/main/java/org/apache/xml/security/utils/EncryptionElementProxy.java b/src/main/java/org/apache/xml/security/utils/EncryptionElementProxy.java
new file mode 100644
index 000000000..c23b33d75
--- /dev/null
+++ b/src/main/java/org/apache/xml/security/utils/EncryptionElementProxy.java
@@ -0,0 +1,67 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.xml.security.utils;
+
+import org.apache.xml.security.exceptions.XMLSecurityException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Class EncryptionElementProxy
+ *
+ */
+public abstract class EncryptionElementProxy extends ElementProxy {
+
+ protected EncryptionElementProxy() {
+ }
+
+ /**
+ * Constructor EncryptionElementProxy
+ *
+ * @param doc the {@link Document} in which Encryption Element
will be placed
+ */
+ public EncryptionElementProxy(Document doc) {
+ if (doc == null) {
+ throw new IllegalArgumentException("Document is null");
+ }
+ setDocument(doc);
+ setElement(XMLUtils.createElementInEncryptionSpace(doc, this.getBaseLocalName()));
+ String prefix = ElementProxy.getDefaultPrefix(this.getBaseNamespace());
+ if (prefix != null && !prefix.isEmpty()) {
+ getElement().setAttribute("xmlns:" + prefix, this.getBaseNamespace());
+ }
+ }
+
+ /**
+ * Constructor EncryptionElementProxy
+ *
+ * @param element Encryption Element
+ * @param baseURI the namespace URI of element
+ * @throws XMLSecurityException if a {@link XMLSecurityException} occurs
+ */
+ public EncryptionElementProxy(Element element, String baseURI) throws XMLSecurityException {
+ super(element, baseURI);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getBaseNamespace() {
+ return EncryptionConstants.EncryptionSpecNS;
+ }
+}
diff --git a/src/main/java/org/apache/xml/security/utils/KeyUtils.java b/src/main/java/org/apache/xml/security/utils/KeyUtils.java
new file mode 100644
index 000000000..4a5d71ba4
--- /dev/null
+++ b/src/main/java/org/apache/xml/security/utils/KeyUtils.java
@@ -0,0 +1,281 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.xml.security.utils;
+
+import org.apache.xml.security.algorithms.implementations.ECDSAUtils;
+import org.apache.xml.security.encryption.XMLEncryptionException;
+import org.apache.xml.security.encryption.params.ConcatKDFParams;
+import org.apache.xml.security.encryption.params.KeyAgreementParameters;
+import org.apache.xml.security.encryption.params.KeyDerivationParameters;
+import org.apache.xml.security.exceptions.DERDecodingException;
+import org.apache.xml.security.exceptions.XMLSecurityException;
+import org.apache.xml.security.encryption.keys.content.derivedKey.ConcatKDF;
+
+import javax.crypto.*;
+import javax.crypto.spec.SecretKeySpec;
+import java.lang.System.Logger.Level;
+import java.security.*;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECGenParameterSpec;
+import java.util.Arrays;
+
+/**
+ * A set of utility methods to handle keys.
+ */
+public class KeyUtils {
+ private static final System.Logger LOG = System.getLogger(KeyUtils.class.getName());
+ /**
+ * Enumeration of Supported key algorithm types.
+ */
+ public enum KeyAlgorithmType {
+ EC("EC", "1.2.840.10045.2.1"),
+ DSA("DSA", "1.2.840.10040.4.1"),
+ RSA("RSA", "1.2.840.113549.1.1.1"),
+ RSASSA_PSS("RSASSA-PSS", "1.2.840.113549.1.1.10"),
+ DH("DiffieHellman", "1.2.840.113549.1.3.1"),
+ XDH("XDH", null),
+ EdDSA("EdDSA", null);
+ private final String jceName;
+ private final String oid;
+
+ KeyAlgorithmType(String jceName, String oid) {
+ this.jceName = jceName;
+ this.oid = oid;
+ }
+
+ public String getJceName() {
+ return jceName;
+ }
+
+ public String getOid() {
+ return oid;
+ }
+
+ }
+
+ /**
+ * Enumeration of specific key types.
+ */
+ public enum KeyType {
+ DSA("DSA", "RFC 8017", KeyAlgorithmType.DSA, "1.2.840.10040.4.1"),
+ RSA("RSA", "RFC 8017", KeyAlgorithmType.RSA, "1.2.840.113549.1.1.1"),
+ RSASSA_PSS("RSASSA-PSS", "RFC 3447", KeyAlgorithmType.RSASSA_PSS, "1.2.840.113549.1.1.10"),
+ DH("DH", "PKCS #3", KeyAlgorithmType.DH, "1.2.840.113549.1.3.1"),
+ SECT163K1("sect163k1", "NIST K-163", KeyAlgorithmType.EC, "1.3.132.0.1"),
+ SECT163R1("sect163r1", "", KeyAlgorithmType.EC, "1.3.132.0.2"),
+ SECT163R2("sect163r2", "NIST B-163", KeyAlgorithmType.EC, "1.3.132.0.15"),
+ SECT193R1("sect193r1", "", KeyAlgorithmType.EC, "1.3.132.0.24"),
+ SECT193R2("sect193r2", "", KeyAlgorithmType.EC, "1.3.132.0.25"),
+ SECT233K1("sect233k1", "NIST K-233", KeyAlgorithmType.EC, "1.3.132.0.26"),
+ SECT233R1("sect233r1", "NIST B-233", KeyAlgorithmType.EC, "1.3.132.0.27"),
+ SECT239K1("sect239k1", "", KeyAlgorithmType.EC, "1.3.132.0.3"),
+ SECT283K1("sect283k1", "NIST K-283", KeyAlgorithmType.EC, "1.3.132.0.16"),
+ SECT283R1("sect283r1", "", KeyAlgorithmType.EC, "1.3.132.0.17"),
+ SECT409K1("sect409k1", "NIST K-409", KeyAlgorithmType.EC, "1.3.132.0.36"),
+ SECT409R1("sect409r1", "NIST B-409", KeyAlgorithmType.EC, "1.3.132.0.37"),
+ SECT571K1("sect571k1", "NIST K-571", KeyAlgorithmType.EC, "1.3.132.0.38"),
+ SECT571R1("sect571r1", "NIST B-571", KeyAlgorithmType.EC, "1.3.132.0.39"),
+ SECP160K1("secp160k1", "", KeyAlgorithmType.EC, "1.3.132.0.9"),
+ SECP160R1("secp160r1", "", KeyAlgorithmType.EC, "1.3.132.0.8"),
+ SECP160R2("secp160r2", "", KeyAlgorithmType.EC, "1.3.132.0.30"),
+ SECP192K1("secp192k1", "", KeyAlgorithmType.EC, "1.3.132.0.31"),
+ SECP192R1("secp192r1", "NIST P-192,X9.62 prime192v1", KeyAlgorithmType.EC, "1.2.840.10045.3.1.1"),
+ SECP224K1("secp224k1", "", KeyAlgorithmType.EC, "1.3.132.0.32"),
+ SECP224R1("secp224r1", "NIST P-224", KeyAlgorithmType.EC, "1.3.132.0.33"),
+ SECP256K1("secp256k1", "", KeyAlgorithmType.EC, "1.3.132.0.10"),
+ SECP256R1("secp256r1", "NIST P-256,X9.62 prime256v1", KeyAlgorithmType.EC, "1.2.840.10045.3.1.7"),
+ SECP384R1("secp384r1", "NIST P-384", KeyAlgorithmType.EC, "1.3.132.0.34"),
+ SECP521R1("secp521r1", "NIST P-521", KeyAlgorithmType.EC, "1.3.132.0.35"),
+ BRAINPOOLP256R1("brainpoolP256r1", "RFC 5639", KeyAlgorithmType.EC, "1.3.36.3.3.2.8.1.1.7"),
+ BRAINPOOLP384R1("brainpoolP384r1", "RFC 5639", KeyAlgorithmType.EC, "1.3.36.3.3.2.8.1.1.11"),
+ BRAINPOOLP512R1("brainpoolP512r1", "RFC 5639", KeyAlgorithmType.EC, "1.3.36.3.3.2.8.1.1.13"),
+ X25519("x25519", "RFC 7748", KeyAlgorithmType.XDH, "1.3.101.110"),
+ X448("x448", "RFC 7748", KeyAlgorithmType.XDH, "1.3.101.111"),
+ ED25519("ed25519", "RFC 8032", KeyAlgorithmType.EdDSA, "1.3.101.112"),
+ ED448("ed448", "RFC 8032", KeyAlgorithmType.EdDSA, "1.3.101.113"),
+ ;
+
+ private final String name;
+ private final String origin;
+ private final KeyAlgorithmType algorithm;
+ private final String oid;
+
+ KeyType(String name, String origin, KeyAlgorithmType algorithm, String oid) {
+ this.name = name;
+ this.origin = origin;
+ this.algorithm = algorithm;
+ this.oid = oid;
+ }
+
+
+ public String getName() {
+ return name;
+ }
+
+ public KeyAlgorithmType getAlgorithm() {
+ return algorithm;
+ }
+
+ public String getOid() {
+ return oid;
+ }
+
+ public String getOrigin() {
+ return origin;
+ }
+
+ public static KeyType getByOid(String oid) {
+ return Arrays.stream(KeyType.values())
+ .filter(keyType -> keyType.getOid().equals(oid))
+ .findFirst().orElse(null);
+ }
+ }
+
+ /**
+ * Method generates DH keypair which match the type of given public key type.
+ *
+ * @param recipientPublicKey public key of recipient
+ * @param provider provider to use for key generation
+ * @return generated keypair
+ * @throws XMLEncryptionException if the keys cannot be generated
+ */
+ public static KeyPair generateEphemeralDHKeyPair(PublicKey recipientPublicKey, Provider provider) throws XMLEncryptionException {
+ String algorithm = recipientPublicKey.getAlgorithm();
+ KeyPairGenerator keyPairGenerator;
+ try {
+
+ if (recipientPublicKey instanceof ECPublicKey) {
+ keyPairGenerator = createKeyPairGenerator(algorithm, provider);
+ ECPublicKey exchangePublicKey = (ECPublicKey) recipientPublicKey;
+ String keyOId = ECDSAUtils.getOIDFromPublicKey(exchangePublicKey);
+ if (keyOId == null) {
+ keyOId = DERDecoderUtils.getAlgorithmIdFromPublicKey(recipientPublicKey);
+ }
+ ECGenParameterSpec kpgparams = new ECGenParameterSpec(keyOId);
+ keyPairGenerator.initialize(kpgparams);
+ } else {
+ String keyOId = DERDecoderUtils.getAlgorithmIdFromPublicKey(recipientPublicKey);
+ KeyType keyType = KeyType.getByOid(keyOId);
+ keyPairGenerator = createKeyPairGenerator(keyType==null?keyOId: keyType.getName() , provider);
+ }
+
+ return keyPairGenerator.generateKeyPair();
+ } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | DERDecodingException e) {
+ throw new XMLEncryptionException(e);
+ }
+ }
+
+ /**
+ * Create a KeyPairGenerator for the given algorithm and provider.
+ *
+ * @param algorithm the key JCE algorithm name
+ * @param provider the provider to use or null if default JCE provider should be used
+ * @return the KeyPairGenerator
+ * @throws NoSuchAlgorithmException if the algorithm is not supported
+ */
+ public static KeyPairGenerator createKeyPairGenerator(String algorithm, Provider provider) throws NoSuchAlgorithmException {
+ return provider == null ? KeyPairGenerator.getInstance(algorithm)
+ : KeyPairGenerator.getInstance(algorithm, provider);
+ }
+
+
+ /**
+ * Method generates a secret key for given KeyAgreementParameterSpec.
+ *
+ * @param parameterSpec KeyAgreementParameterSpec which defines algorithm to derive key
+ * @return generated secret key
+ * @throws XMLEncryptionException if the secret key cannot be generated as: Key agreement is not supported,
+ * wrong key types, etc.
+ */
+ public static SecretKey aesWrapKeyWithDHGeneratedKey(KeyAgreementParameters parameterSpec)
+ throws XMLEncryptionException {
+ try {
+ PublicKey publicKey = parameterSpec.getAgreementPublicKey();
+ PrivateKey privateKey = parameterSpec.getAgreementPrivateKey();
+
+ String algorithm = publicKey.getAlgorithm();
+ if ("EC".equalsIgnoreCase(algorithm)) {
+ LOG.log(Level.WARNING, "EC keys are detected for key agreement algorithm! " +
+ "Cryptographic algorithm may not be secure, consider using a different algorithm (and keys).");
+ }
+ algorithm = algorithm + (algorithm.equalsIgnoreCase("EC") ? "DH" : "");
+ KeyAgreement keyAgreement = KeyAgreement.getInstance(algorithm);
+ keyAgreement.init(privateKey);
+ keyAgreement.doPhase(publicKey, true);
+ byte[] secret = keyAgreement.generateSecret();
+ byte[] kek = deriveKeyEncryptionKey(secret, parameterSpec.getKeyDerivationParameter());
+ return new SecretKeySpec(kek, "AES");
+
+
+ } catch (XMLSecurityException | NoSuchAlgorithmException | InvalidKeyException e) {
+ throw new XMLEncryptionException(e);
+ }
+ }
+
+ /**
+ * Defines the key size for the encrypting algorithm.
+ *
+ * @param keyWrapAlg the key wrap algorithm URI
+ * @return the key size in bits
+ * @throws XMLEncryptionException if the key wrap algorithm is not supported
+ */
+ public static int getAESKeyBitSizeForWrapAlgorithm(String keyWrapAlg) throws XMLEncryptionException {
+ switch (keyWrapAlg) {
+ case EncryptionConstants.ALGO_ID_KEYWRAP_AES128:
+ return 128;
+ case EncryptionConstants.ALGO_ID_KEYWRAP_AES192:
+ return 192;
+ case EncryptionConstants.ALGO_ID_KEYWRAP_AES256:
+ return 256;
+ default:
+ throw new XMLEncryptionException("Unsupported KeyWrap Algorithm");
+ }
+ }
+
+
+ /**
+ * Derive a key encryption key from a shared secret and keyDerivationParameter. Currently only the ConcatKDF is supported.
+ * @param sharedSecret the shared secret
+ * @param keyDerivationParameter the key derivation parameters
+ * @return the derived key encryption key
+ * @throws XMLSecurityException if the key derivation algorithm is not supported
+ */
+ public static byte[] deriveKeyEncryptionKey(byte[] sharedSecret, KeyDerivationParameters keyDerivationParameter)
+ throws XMLSecurityException {
+ int iKeySize = keyDerivationParameter.getKeyBitLength()/8;
+ String keyDerivationAlgorithm = keyDerivationParameter.getAlgorithm();
+ if (!EncryptionConstants.ALGO_ID_KEYDERIVATION_CONCATKDF.equals(keyDerivationAlgorithm)) {
+ throw new XMLEncryptionException( "unknownAlgorithm",
+ keyDerivationAlgorithm);
+ }
+ ConcatKDFParams ckdfParameter = (ConcatKDFParams) keyDerivationParameter;
+
+ // get parameters
+ String digestAlgorithm = ckdfParameter.getDigestAlgorithm();
+
+ String algorithmID = ckdfParameter.getAlgorithmID();
+ String partyUInfo = ckdfParameter.getPartyUInfo();
+ String partyVInfo = ckdfParameter.getPartyVInfo();
+ String suppPubInfo = ckdfParameter.getSuppPubInfo();
+ String suppPrivInfo = ckdfParameter.getSuppPrivInfo();
+
+ ConcatKDF concatKDF = new ConcatKDF(digestAlgorithm);
+ return concatKDF.deriveKey(sharedSecret, algorithmID, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo, iKeySize);
+ }
+}
diff --git a/src/main/java/org/apache/xml/security/utils/XMLUtils.java b/src/main/java/org/apache/xml/security/utils/XMLUtils.java
index 3ad292c90..9027469cd 100644
--- a/src/main/java/org/apache/xml/security/utils/XMLUtils.java
+++ b/src/main/java/org/apache/xml/security/utils/XMLUtils.java
@@ -706,6 +706,20 @@ public static Element selectXencNode(Node sibling, String nodeName, int number)
return null;
}
+ /**
+ * Helper method to get the "number"-th element for a given local
+ * name and namespace: http://www.w3.org/2009/xmlenc11#. If element with given search parameters is not found,
+ * null is returned.
+ *
+ * @param sibling the sibling node from which to start searching
+ * @param nodeName the local name of the element to search for
+ * @param number the index of the element to search for
+ * @return node with the given node name or null if not found.
+ */
+ public static Element selectXenc11Node(Node sibling, String nodeName, int number) {
+ return selectNode(sibling, EncryptionConstants.EncryptionSpec11NS, nodeName, number);
+ }
+
/**
* @param sibling
* @param uri
diff --git a/src/test/java/org/apache/xml/security/test/dom/encryption/XMLCipherTest.java b/src/test/java/org/apache/xml/security/test/dom/encryption/XMLCipherTest.java
index 554cca280..b187f9dea 100644
--- a/src/test/java/org/apache/xml/security/test/dom/encryption/XMLCipherTest.java
+++ b/src/test/java/org/apache/xml/security/test/dom/encryption/XMLCipherTest.java
@@ -28,12 +28,10 @@
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.nio.charset.StandardCharsets;
-import java.security.Key;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.*;
+import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
@@ -44,6 +42,7 @@
import javax.crypto.spec.SecretKeySpec;
import org.apache.xml.security.algorithms.JCEMapper;
+import org.apache.xml.security.algorithms.MessageDigestAlgorithm;
import org.apache.xml.security.c14n.Canonicalizer;
import org.apache.xml.security.encryption.CipherData;
import org.apache.xml.security.encryption.EncryptedData;
@@ -52,14 +51,24 @@
import org.apache.xml.security.encryption.EncryptionProperties;
import org.apache.xml.security.encryption.EncryptionProperty;
import org.apache.xml.security.encryption.XMLCipher;
+import org.apache.xml.security.encryption.keys.KeyInfoEnc;
+import org.apache.xml.security.encryption.params.ConcatKDFParams;
+import org.apache.xml.security.encryption.params.KeyAgreementParameters;
+import org.apache.xml.security.encryption.params.KeyDerivationParameters;
import org.apache.xml.security.keys.KeyInfo;
import org.apache.xml.security.parser.XMLParserException;
import org.apache.xml.security.test.dom.TestUtils;
+import org.apache.xml.security.testutils.JDKTestUtils;
+import org.apache.xml.security.testutils.KeyTestUtils;
import org.apache.xml.security.transforms.params.XPathContainer;
import org.apache.xml.security.utils.EncryptionConstants;
+import org.apache.xml.security.utils.KeyUtils;
import org.apache.xml.security.utils.XMLUtils;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -128,6 +137,13 @@ public XMLCipherTest() throws Exception {
JCEMapper.translateURItoJCEID(EncryptionConstants.ALGO_ID_KEYWRAP_AES128) != null;
}
+ @AfterEach
+ public void afterTest() throws Exception {
+ // remove the dynamically installed provider
+ if (JDKTestUtils.getAuxiliaryProvider()!=null) {
+ Security.removeProvider(JDKTestUtils.getAuxiliaryProvider().getName());
+ }
+ }
/**
* Test encryption using a generated AES 128 bit key that is
* encrypted using a AES 192 bit key. Then reverse using the KEK
@@ -268,6 +284,92 @@ void testAES256ElementRSAKWCipherUsingKEK() throws Exception {
}
}
+ /**
+ * Test that encrypt and decrypt using ECDH-ES for key encryption
+ *
+ * @throws Exception Thrown when there is any problem in signing or verification
+ */
+ @ParameterizedTest
+ @EnumSource(value = KeyUtils.KeyType.class, mode = EnumSource.Mode.INCLUDE,
+ names = {"SECP256R1", "SECP384R1", "SECP521R1", "X25519", "X448"})
+ void testAES128ElementEcdhEsKWCipher(KeyUtils.KeyType keyType) throws Exception {
+ // Skip test for IBM JDK
+ Assumptions.assumeTrue(haveISOPadding,
+ "Test testAES128ElementEcdhEsKWCipher for key ["+keyType+"] was skipped as necessary algorithms not available!" );
+ Assumptions.assumeTrue(JDKTestUtils.isAlgorithmSupported(keyType.getAlgorithm().getJceName(), true),
+ "Test testAES128ElementEcdhEsKWCipher for key ["+keyType+"] was skipped as necessary algorithms not available!" );
+
+ // init parameters encrypted key object
+ String dataEncryptionAlgorithm = XMLCipher.AES_256_GCM;
+ String keyWrapAlgorithm = XMLCipher.AES_128_KeyWrap;
+ int transportKeyBitLength = 128;
+
+ // prepare the test document
+ Document d = document(); // source
+ Document ed = null;
+ Document dd = null;
+ Element e = (Element) d.getElementsByTagName(element()).item(index());
+ Element ee = null;
+ String source = null;
+ String target = null;
+
+ source = toString(d);
+
+ // Generate test recipient key pair
+ KeyPair recipientKeyPair = KeyTestUtils.generateKeyPair(keyType);
+ PrivateKey privRecipientKey = recipientKeyPair.getPrivate();
+ PublicKey pubRecipientKey = recipientKeyPair.getPublic();
+
+ // Generate a traffic key
+ KeyGenerator keygen = KeyGenerator.getInstance("AES");
+ keygen.init(transportKeyBitLength);
+ Key ephemeralSymmetricKey = keygen.generateKey();
+
+
+ XMLCipher cipherEncKey = XMLCipher.getInstance(keyWrapAlgorithm);
+ cipherEncKey.init(XMLCipher.WRAP_MODE, pubRecipientKey);
+ cipherEncKey.setSecureValidation(true);
+ // create key agreement parameters
+ int keyBitLen = KeyUtils.getAESKeyBitSizeForWrapAlgorithm(keyWrapAlgorithm);
+ KeyDerivationParameters keyDerivationParameter = new ConcatKDFParams(keyBitLen,
+ MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA256);
+ AlgorithmParameterSpec parameterSpec = new KeyAgreementParameters(
+ KeyAgreementParameters.ActorType.ORIGINATOR,
+ EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES,
+ keyDerivationParameter);
+ // encrypt transport key with KeyAgreement
+ EncryptedKey encryptedKey = cipherEncKey.encryptKey(d, ephemeralSymmetricKey, parameterSpec, null);
+ assertEquals(1, ((KeyInfoEnc)encryptedKey.getKeyInfo()).lengthAgreementMethod());
+
+
+ // encrypt data
+ XMLCipher cipherEncData = XMLCipher.getInstance(dataEncryptionAlgorithm);
+ cipherEncData.init(XMLCipher.ENCRYPT_MODE, ephemeralSymmetricKey);
+ EncryptedData builder = cipherEncData.getEncryptedData();
+ // add encrypted key to key info in encrypted data
+ KeyInfo builderKeyInfo = builder.getKeyInfo();
+ if (builderKeyInfo == null) {
+ builderKeyInfo = new KeyInfo(d);
+ builder.setKeyInfo(builderKeyInfo);
+ }
+ builderKeyInfo.add(encryptedKey);
+
+ ed = cipherEncData.doFinal(d, e);
+
+ Files.write(Paths.get("target","test-enc-"+keyType.name()+".xml"), toString(ed).getBytes());
+
+ //decrypt
+ ee = (Element) ed.getElementsByTagName("xenc:EncryptedData").item(0);
+ XMLCipher cipherDecData = XMLCipher.getInstance(dataEncryptionAlgorithm);
+ cipherDecData.init(XMLCipher.DECRYPT_MODE, null);
+ cipherDecData.setKEK(privRecipientKey);
+ cipherDecData.setSecureValidation(true);
+ dd = cipherDecData.doFinal(ed, ee);
+
+ target = toString(dd);
+ assertEquals(source, target);
+ }
+
/**
* Test encryption using a generated AES 192 bit key that is
* encrypted using a 3DES key. Then reverse by decrypting
@@ -1004,4 +1106,4 @@ private int index() {
return Integer.parseInt(elementIndex);
}
-}
\ No newline at end of file
+}
diff --git a/src/test/java/org/apache/xml/security/test/dom/encryption/XMLEncryption11Test.java b/src/test/java/org/apache/xml/security/test/dom/encryption/XMLEncryption11Test.java
index 4d15bc5b2..08175d221 100644
--- a/src/test/java/org/apache/xml/security/test/dom/encryption/XMLEncryption11Test.java
+++ b/src/test/java/org/apache/xml/security/test/dom/encryption/XMLEncryption11Test.java
@@ -18,12 +18,12 @@
*/
package org.apache.xml.security.test.dom.encryption;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
+import java.io.*;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
@@ -33,36 +33,48 @@
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
+import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.xml.security.algorithms.JCEMapper;
+import org.apache.xml.security.c14n.Canonicalizer;
+import org.apache.xml.security.encryption.AgreementMethod;
import org.apache.xml.security.encryption.EncryptedData;
import org.apache.xml.security.encryption.EncryptedKey;
import org.apache.xml.security.encryption.XMLCipher;
+import org.apache.xml.security.encryption.keys.KeyInfoEnc;
+import org.apache.xml.security.encryption.params.ConcatKDFParams;
+import org.apache.xml.security.encryption.params.KeyAgreementParameters;
+import org.apache.xml.security.encryption.params.KeyDerivationParameters;
import org.apache.xml.security.keys.KeyInfo;
import org.apache.xml.security.keys.content.X509Data;
import org.apache.xml.security.keys.content.x509.XMLX509Certificate;
+import org.apache.xml.security.parser.XMLParserException;
import org.apache.xml.security.test.dom.DSNamespaceContext;
import org.apache.xml.security.utils.Constants;
import org.apache.xml.security.utils.EncryptionConstants;
+import org.apache.xml.security.utils.KeyUtils;
import org.apache.xml.security.utils.XMLUtils;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import static org.apache.xml.security.test.XmlSecTestEnvironment.resolveFile;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
@@ -72,11 +84,16 @@
* http://www.w3.org/2008/xmlsec/Drafts/xmlenc-core-11/test-cases/
*
* Note: I had to convert the given .p12 file into a .jks as it could not be loaded with KeyStore.
- *
- * TODO As of now all of the KeyWrapping tests are supported, but none of the KeyAgreement tests.
*/
class XMLEncryption11Test {
+ private static final DocumentBuilderFactory DEFAULT_DOCUMENT_BUILDER_FACTORY;
+ static {
+ DEFAULT_DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
+ DEFAULT_DOCUMENT_BUILDER_FACTORY.setNamespaceAware(true);
+ }
+ private static final String RESOURCE_FOLDER = "/org/w3c/www/interop/xmlenc-core-11/";
+
private static String cardNumber;
private static int nodeCount = 0;
private boolean haveISOPadding;
@@ -515,35 +532,268 @@ void testKeyWrappingRSA4096EncryptDecryptSHA224() throws Exception {
// XMLUtils.outputDOM(dd.getFirstChild(), System.out);
checkDecryptedDoc(dd, true);
} else {
- LOG.log(Level.WARNING, "Skipping testRSA2048 as necessary crypto algorithms are not available");
+ LOG.log(Level.WARNING,
+ "Skipping testRSA2048 as necessary "
+ + "crypto algorithms are not available"
+ );
}
}
+ /**
+ * The KeyAgreement test cases from the W3C test suite using XML as input.
+ *
+ * https://www.w3.org/2008/xmlsec/Drafts/xmlenc-core-11/test-cases/#sec-KeyAgreement
+ */
+ @ParameterizedTest
+ @CsvSource({
+ "AGRMNT.1, plaintext.xml, EC-P256_SHA256WithECDSA-v02.p12, PKCS12, passwd, test-certificate, http://www.w3.org/2001/04/xmlenc#kw-aes128, http://www.w3.org/2009/xmlenc11#aes128-gcm, http://www.w3.org/2001/04/xmlenc#sha256",
+ "AGRMNT.2, plaintext.xml, EC-P384_SHA256WithECDSA-v02.p12, PKCS12, passwd, test-certificate, http://www.w3.org/2001/04/xmlenc#kw-aes192, http://www.w3.org/2009/xmlenc11#aes192-gcm, http://www.w3.org/2001/04/xmldsig-more#sha384",
+ "AGRMNT.3, plaintext.xml, EC-P521_SHA256WithECDSA-v02.p12, PKCS12, passwd, test-certificate, http://www.w3.org/2001/04/xmlenc#kw-aes256, http://www.w3.org/2009/xmlenc11#aes256-gcm, http://www.w3.org/2001/04/xmlenc#sha512",
+ })
+ void testAgreementKeyEncryptDecryptDocument(String w3cTag,
+ String data,
+ String keystoreFile, String keystoreType,
+ String passwd, String alias,
+ String keyWrapAlgorithm,
+ String encryptionAlgorithm,
+ String kdfAlgorithm) throws Exception {
+ Assumptions.assumeTrue(haveISOPadding,
+ "Skipping testAgreementKey ["+w3cTag+"] as necessary crypto algorithms are not available");
+
+ KeyStore keyStore = loadKeyStoreFromResource(keystoreFile, passwd, keystoreType);
+ Certificate cert = keyStore.getCertificate(alias);
+
+ KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry)
+ keyStore.getEntry(alias, new KeyStore.PasswordProtection(passwd.toCharArray()));
+ PrivateKey ecKey = pkEntry.getPrivateKey();
+
+ // Perform encryption
+ Document doc = loadDocumentFromResource(data);
+ Key sessionKey = getSessionKey(encryptionAlgorithm);
+
+
+ int keyBitLen = KeyUtils.getAESKeyBitSizeForWrapAlgorithm(keyWrapAlgorithm);
+ KeyDerivationParameters keyDerivationParameter = new ConcatKDFParams(keyBitLen, kdfAlgorithm);
+ AlgorithmParameterSpec parameterSpec = new KeyAgreementParameters(
+ KeyAgreementParameters.ActorType.ORIGINATOR,
+ EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES,
+ keyDerivationParameter);
+
+
+ EncryptedKey encryptedKey =
+ createEncryptedKey(
+ doc,
+ (X509Certificate)cert,
+ sessionKey,
+ keyWrapAlgorithm,
+ parameterSpec, null);
+
+
+ doc = encryptDocument(
+ doc,
+ encryptedKey,
+ sessionKey,
+ encryptionAlgorithm
+ );
+
+ Files.write(Paths.get("target","test-enc-"+w3cTag+".xml"), toString(doc.getFirstChild()).getBytes());
+ // XMLUtils.outputDOM(doc.getFirstChild(), System.out);
+
+ // Perform decryption
+ Document dd = decryptElement(doc, ecKey, (X509Certificate)cert);
+ // XMLUtils.outputDOM(dd.getFirstChild(), System.out);
+ checkDecryptedDoc(dd, true);
+ }
+
+ /**
+ * The KeyAgreement test cases from the W3C test suite using XML as input. The method decrypts the document from
+ * test page and compares the result with the expected result.
+ *
+ * https://www.w3.org/2008/xmlsec/Drafts/xmlenc-core-11/test-cases/#sec-KeyAgreement
+ */
+ @ParameterizedTest
+ @CsvSource({
+ "AGRMNT.1-dec, cipherText__EC-P256__aes128-gcm__kw-aes128__ECDH-ES__ConcatKDF-1.xml, EC-P256_SHA256WithECDSA-v02.p12, PKCS12, passwd, test-certificate",
+ "AGRMNT.2-dec, cipherText__EC-P384__aes192-gcm__kw-aes192__ECDH-ES__ConcatKDF-2.xml, EC-P384_SHA256WithECDSA-v02.p12, PKCS12, passwd, test-certificate",
+ "AGRMNT.3-dec, cipherText__EC-P521__aes256-gcm__kw-aes256__ECDH-ES__ConcatKDF-3.xml, EC-P521_SHA256WithECDSA-v02.p12, PKCS12, passwd, test-certificate",
+ })
+ void testAgreementKeyDecryptDocument(String w3cTag,
+ String encryptedData,
+ String keystoreFile, String keystoreType,
+ String passwd, String alias) throws Exception {
+ Assumptions.assumeTrue(haveISOPadding,
+ "Skipping testAgreementKey ["+w3cTag+"] as necessary crypto algorithms are not available");
+
+ KeyStore keyStore = loadKeyStoreFromResource(keystoreFile, passwd, keystoreType);
+ Certificate cert = keyStore.getCertificate(alias);
+
+ KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry)
+ keyStore.getEntry(alias, new KeyStore.PasswordProtection(passwd.toCharArray()));
+ PrivateKey ecKey = pkEntry.getPrivateKey();
+
+ // get encrypted data from test page
+ Document ecryptedXmlDocument = loadDocumentFromResource(encryptedData);
+ // Perform decryption
+ Document dd = decryptElement(ecryptedXmlDocument, ecKey, (X509Certificate)cert);
+ checkDecryptedDoc(dd, true);
+ }
+
+ /**
+ * The KeyAgreement test cases from the W3C test suite using bytearray as input.
+ *
+ * https://www.w3.org/2008/xmlsec/Drafts/xmlenc-core-11/test-cases/#sec-KeyAgreement
+ */
+ @ParameterizedTest
+ @CsvSource({
+ "AGRMNT.4, binary-data.hex, EC-P256.pfx, PKCS12, 1234, certreq-5b4623c8-5790-4b32-b59f-540c8bcfda4a, http://www.w3.org/2001/04/xmlenc#kw-aes128, http://www.w3.org/2009/xmlenc11#aes128-gcm, http://www.w3.org/2001/04/xmlenc#sha256",
+ "AGRMNT.5, binary-data.hex, EC-P384.pfx, PKCS12, 1234, certreq-4c7c6242-e408-4391-a7e7-1a87a2ef2ba8, http://www.w3.org/2001/04/xmlenc#kw-aes192, http://www.w3.org/2009/xmlenc11#aes192-gcm, http://www.w3.org/2001/04/xmldsig-more#sha384",
+ "AGRMNT.6, binary-data.hex, EC-P521.pfx, PKCS12, 1234, certreq-61afb173-5eab-475a-8c54-0cb792c82820, http://www.w3.org/2001/04/xmlenc#kw-aes256, http://www.w3.org/2009/xmlenc11#aes256-gcm, http://www.w3.org/2001/04/xmlenc#sha512"
+ })
+ void testAgreementKeyEncryptDecryptData(String w3cTag,
+ String resourceHexFileName,
+ String keystoreFile, String keystoreType,
+ String passwd, String alias,
+ String keyWrapAlgorithm,
+ String encryptionAlgorithm,
+ String kdfAlgorithm) throws Exception {
+ Assumptions.assumeTrue(haveISOPadding,
+ "Skipping testAgreementKey ["+w3cTag+"] as necessary crypto algorithms are not available");
+
+ KeyStore keyStore = loadKeyStoreFromResource(keystoreFile, passwd, keystoreType);
+ Certificate cert = keyStore.getCertificate(alias);
+
+ KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry)
+ keyStore.getEntry(alias, new KeyStore.PasswordProtection(passwd.toCharArray()));
+ PrivateKey ecKey = pkEntry.getPrivateKey();
+
+ // Perform encryption
+ byte[] testData = hexFileContentByteArray(resourceHexFileName);
+ // create empty document for EncryptedKey
+ Document doc = DEFAULT_DOCUMENT_BUILDER_FACTORY.newDocumentBuilder().newDocument();
+
+ Key sessionKey = getSessionKey(encryptionAlgorithm);
+
+
+ int keyBitLen = KeyUtils.getAESKeyBitSizeForWrapAlgorithm(keyWrapAlgorithm);
+ KeyDerivationParameters keyDerivationParameter = new ConcatKDFParams(keyBitLen, kdfAlgorithm);
+ AlgorithmParameterSpec parameterSpec = new KeyAgreementParameters(
+ KeyAgreementParameters.ActorType.ORIGINATOR,
+ EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES,
+ keyDerivationParameter);
+
+
+ EncryptedKey encryptedKey =
+ createEncryptedKey(
+ doc,
+ (X509Certificate)cert,
+ sessionKey,
+ keyWrapAlgorithm,
+ parameterSpec, null);
+
+
+ doc = encryptData(
+ doc,
+ encryptedKey,
+ sessionKey,
+ encryptionAlgorithm,
+ new ByteArrayInputStream(testData)
+ );
+
+ Files.write(Paths.get("target","test-enc-"+w3cTag+".xml"), toString(doc.getFirstChild()).getBytes());
+ // Perform decryption
+ byte[] result = decryptData(doc, ecKey, (X509Certificate)cert);
+ // XMLUtils.outputDOM(dd.getFirstChild(), System.out);
+ assertNotNull(result);
+ assertArrayEquals(testData, result);
+ }
+
+
+ /**
+ * The KeyAgreement test cases from the W3C test suite using bytearray as input.
+ *
+ * https://www.w3.org/2008/xmlsec/Drafts/xmlenc-core-11/test-cases/#sec-KeyAgreement
+ */
+ @ParameterizedTest
+ @CsvSource({
+ "AGRMNT.4-dec, binary-data.hex, cipherText__EC-P256__aes128-gcm__kw-aes128__ECDH-ES__ConcatKDF-4.xml, EC-P256.pfx, PKCS12, 1234, certreq-5b4623c8-5790-4b32-b59f-540c8bcfda4a",
+ "AGRMNT.5-dec, binary-data.hex, cipherText__EC-P384__aes192-gcm__kw-aes192__ECDH-ES__ConcatKDF-5.xml, EC-P384.pfx, PKCS12, 1234, certreq-4c7c6242-e408-4391-a7e7-1a87a2ef2ba8",
+ "AGRMNT.6-dec, binary-data.hex, cipherText__EC-P521__aes256-gcm__kw-aes256__ECDH-ES__ConcatKDF-6.xml, EC-P521.pfx, PKCS12, 1234, certreq-61afb173-5eab-475a-8c54-0cb792c82820"
+ })
+ void testAgreementKeyDecryptData(String w3cTag,
+ String resourceHexFileName,
+ String decryptResourceData,
+ String keystoreFile, String keystoreType,
+ String passwd, String alias) throws Exception {
+ Assumptions.assumeTrue(haveISOPadding,
+ "Skipping testAgreementKey ["+w3cTag+"] as necessary crypto algorithms are not available");
+
+ KeyStore keyStore = loadKeyStoreFromResource(keystoreFile, passwd, keystoreType);
+ Certificate cert = keyStore.getCertificate(alias);
+
+ KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry)
+ keyStore.getEntry(alias, new KeyStore.PasswordProtection(passwd.toCharArray()));
+ PrivateKey ecKey = pkEntry.getPrivateKey();
+
+ // Perform encryption
+ byte[] testData = hexFileContentByteArray(resourceHexFileName);
+
+ // get encrypted data from test page
+ Document doc = loadDocumentFromResource(decryptResourceData);
+
+ // Perform decryption
+ byte[] result = decryptData(doc, ecKey, (X509Certificate)cert);
+ // compare results
+ assertNotNull(result);
+ assertArrayEquals(testData, result);
+ }
+
private KeyStore loadKeyStore(File keystore)
- throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, FileNotFoundException {
- KeyStore keyStore = KeyStore.getInstance("jks");
+ throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
+ KeyStore keyStore = KeyStore.getInstance(getKeystoreTypeForFileName(keystore.getName()));
try (FileInputStream inputStream = new FileInputStream(keystore)) {
keyStore.load(inputStream, "passwd".toCharArray());
}
return keyStore;
}
+ private KeyStore loadKeyStoreFromResource(String filename, String passwd, String keystoreType)
+ throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
+
+ KeyStore keyStore = KeyStore.getInstance(keystoreType);
+ try ( InputStream keystoreIS = getResourceInputStream(filename);) {
+ keyStore.load(keystoreIS, passwd!=null?passwd.toCharArray():null);
+ }
+ return keyStore;
+ }
+
+ private Document loadDocumentFromResource(String resourceName)
+ throws IOException, XMLParserException {
+
+ try (InputStream dataXMLInputStream = getResourceInputStream(resourceName)){
+ return XMLUtils.read(dataXMLInputStream, false);
+ }
+ }
+
+ private String getKeystoreTypeForFileName(String filename){
+ return filename.toLowerCase().endsWith(".p12") ? "PKCS12" : "JKS";
+ }
+
/**
* Method decryptElement
*
* Take a key, encryption type and a file, find an encrypted element
* decrypt it and return the resulting document
*
- * @param filename File to decrypt from
- * @param key The Key to use for decryption
+ * @param decKey The Key to use for decryption
+ * @param encCert The certificate used to encrypt the key
*/
- private Document decryptElement(File file, Key rsaKey, X509Certificate rsaCert) throws Exception {
+ private Document decryptElement(File file, Key decKey, X509Certificate encCert) throws Exception {
// Parse the document in question
Document doc;
try (FileInputStream inputStream = new FileInputStream(file)) {
doc = XMLUtils.read(inputStream, false);
}
- return decryptElement(doc, rsaKey, rsaCert);
+ return decryptElement(doc, decKey, encCert);
}
/**
@@ -552,10 +802,11 @@ private Document decryptElement(File file, Key rsaKey, X509Certificate rsaCert)
* Take a key, encryption type and a document, find an encrypted element
* decrypt it and return the resulting document
*
- * @param filename File to decrypt from
- * @param key The Key to use for decryption
+ * @param doc the XML document wrrapping the encrypted data
+ * @param decKey The Key to use for decryption
+ * @param encCert The certificate used to encrypt the key
*/
- private Document decryptElement(Document doc, Key rsaKey, X509Certificate rsaCert) throws Exception {
+ private Document decryptElement(Document doc, Key decKey, X509Certificate encCert) throws Exception {
// Create the XMLCipher element
XMLCipher cipher = XMLCipher.getInstance();
@@ -566,14 +817,18 @@ private Document decryptElement(Document doc, Key rsaKey, X509Certificate rsaCer
KeyInfo ki = encryptedData.getKeyInfo();
EncryptedKey encryptedKey = ki.itemEncryptedKey(0);
- KeyInfo kiek = encryptedKey.getKeyInfo();
+ KeyInfoEnc kiek = (KeyInfoEnc)encryptedKey.getKeyInfo();
+ if (kiek.containsAgreementMethod()){
+ AgreementMethod agreementMethod = kiek.itemAgreementMethod(0);
+ kiek = agreementMethod.getRecipientKeyInfo();
+ }
X509Data certData = kiek.itemX509Data(0);
XMLX509Certificate xcert = certData.itemCertificate(0);
X509Certificate cert = xcert.getX509Certificate();
- assertEquals(rsaCert, cert);
+ assertEquals(encCert, cert);
XMLCipher cipher2 = XMLCipher.getInstance();
- cipher2.init(XMLCipher.UNWRAP_MODE, rsaKey);
+ cipher2.init(XMLCipher.UNWRAP_MODE, decKey);
Key key = cipher2.decryptKey(encryptedKey, encryptedData.getEncryptionMethod().getAlgorithm());
cipher.init(XMLCipher.DECRYPT_MODE, key);
@@ -582,6 +837,46 @@ private Document decryptElement(Document doc, Key rsaKey, X509Certificate rsaCer
return dd;
}
+ /**
+ * Method decryptElement
+ *
+ * Take a key, encryption type and a document, find an encrypted element
+ * decrypt it and return the resulting document
+ *
+ * @param doc the XML document wrrapping the encrypted data
+ * @param decKey The Key to use for decryption
+ * @param rsaCert The certificate used to encrypt the key
+ *
+ */
+ private byte[] decryptData(Document doc, Key decKey, X509Certificate rsaCert) throws Exception {
+ // Create the XMLCipher element
+ XMLCipher cipher = XMLCipher.getInstance();
+
+ // Need to pre-load the Encrypted Data so we can get the key info
+ Element ee = (Element) doc.getElementsByTagNameNS("http://www.w3.org/2001/04/xmlenc#", "EncryptedData").item(0);
+ cipher.init(XMLCipher.DECRYPT_MODE, null);
+ EncryptedData encryptedData = cipher.loadEncryptedData(doc, ee);
+
+ KeyInfo ki = encryptedData.getKeyInfo();
+ EncryptedKey encryptedKey = ki.itemEncryptedKey(0);
+ KeyInfoEnc kiek = (KeyInfoEnc)encryptedKey.getKeyInfo();
+ if (kiek.containsAgreementMethod()){
+ AgreementMethod agreementMethod = kiek.itemAgreementMethod(0);
+ kiek = agreementMethod.getRecipientKeyInfo();
+ }
+ X509Data certData = kiek.itemX509Data(0);
+ XMLX509Certificate xcert = certData.itemCertificate(0);
+ X509Certificate cert = xcert.getX509Certificate();
+ assertEquals(rsaCert, cert);
+
+ XMLCipher cipher2 = XMLCipher.getInstance();
+ cipher2.init(XMLCipher.UNWRAP_MODE, decKey);
+ Key key = cipher2.decryptKey(encryptedKey, encryptedData.getEncryptionMethod().getAlgorithm());
+
+ cipher.init(XMLCipher.DECRYPT_MODE, key);
+ return cipher.decryptToByteArray(ee);
+ }
+
/**
* Create an EncryptedKey object using the given parameters.
*/
@@ -628,6 +923,39 @@ private EncryptedKey createEncryptedKey(
return encryptedKey;
}
+ private EncryptedKey createEncryptedKey(
+ Document doc,
+ X509Certificate cert,
+ Key sessionKey,
+ String encryptionMethod,
+ AlgorithmParameterSpec params,
+ SecureRandom random
+ ) throws Exception {
+ // Create the XMLCipher element
+ XMLCipher cipher = XMLCipher.getInstance(encryptionMethod, null, null);
+
+ cipher.init(XMLCipher.WRAP_MODE, cert.getPublicKey());
+
+ EncryptedKey encryptedKey = cipher.encryptKey(doc, sessionKey, params, random);
+
+ KeyInfo builderKeyInfo = encryptedKey.getKeyInfo();
+ if (builderKeyInfo == null) {
+ builderKeyInfo = new KeyInfoEnc(doc);
+ encryptedKey.setKeyInfo(builderKeyInfo);
+ }
+
+ X509Data x509Data = new X509Data(doc);
+ x509Data.addCertificate(cert);
+ if (builderKeyInfo instanceof KeyInfoEnc
+ && ((KeyInfoEnc)builderKeyInfo).lengthAgreementMethod()>0) {
+ AgreementMethod agreementMethod = ((KeyInfoEnc)builderKeyInfo).itemAgreementMethod(0);
+ agreementMethod.getRecipientKeyInfo().add(x509Data);
+ } else {
+ builderKeyInfo.add(x509Data);
+ }
+ return encryptedKey;
+ }
+
/**
* Generate a session key using the given algorithm
*/
@@ -671,6 +999,36 @@ private Document encryptDocument(
}
+ /**
+ * Encrypt a Document using the given parameters.
+ */
+ private Document encryptData(
+ Document doc,
+ EncryptedKey encryptedKey,
+ Key sessionKey,
+ String encryptionMethod,
+ InputStream dataToEncrypt
+ ) throws Exception {
+ // Create the XMLCipher element
+ XMLCipher cipher = XMLCipher.getInstance(encryptionMethod);
+
+ cipher.init(XMLCipher.ENCRYPT_MODE, sessionKey);
+ EncryptedData builder = cipher.getEncryptedData();
+
+ KeyInfo builderKeyInfo = builder.getKeyInfo();
+ if (builderKeyInfo == null) {
+ builderKeyInfo = new KeyInfo(doc);
+ builder.setKeyInfo(builderKeyInfo);
+ }
+
+ builderKeyInfo.add(encryptedKey);
+
+ EncryptedData endData = cipher.encryptData(doc, null, dataToEncrypt);
+ Element encDataElement = cipher.martial(endData);
+ doc.appendChild(encDataElement);
+ return doc;
+ }
+
/**
* Method countNodes
*
@@ -746,4 +1104,39 @@ private void checkDecryptedDoc(Document d, boolean doNodeCheck) throws Exception
}
}
-}
\ No newline at end of file
+ public static byte[] hexFileContentByteArray(String fileName) throws IOException {
+ byte[] data;
+ try (InputStream is = getResourceInputStream(fileName)) {
+ int l = is.available() / 2;
+ data = new byte[l];
+ byte[] charByte = new byte[2];
+ for (int i = 0; i < l; i++) {
+ is.read(charByte);
+ data[i] = (byte) ((Character.digit(charByte[0], 16) << 4)
+ + Character.digit(charByte[1], 16));
+ }
+ }
+ return data;
+ }
+
+ /**
+ * Method returns a resource input stream object from resources folder '/org/w3c/www/interop/xmlenc-core-11/'
+ * @param resourceName name of the resource file
+ * @return InputStream object or null if resource not found
+ */
+ public static InputStream getResourceInputStream(String resourceName) {
+ return XMLEncryption11Test.class.getResourceAsStream(RESOURCE_FOLDER + resourceName);
+ }
+
+
+ private String toString (Node n) throws Exception {
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ Canonicalizer c14n = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_OMIT_COMMENTS);
+
+ c14n.canonicalizeSubtree(n, baos);
+ baos.flush();
+
+ return baos.toString(StandardCharsets.UTF_8);
+ }
+ }
+}
diff --git a/src/test/java/org/apache/xml/security/test/dom/keys/DEREncodedKeyValueTest.java b/src/test/java/org/apache/xml/security/test/dom/keys/DEREncodedKeyValueTest.java
index 6ac1bc838..2b583b6a0 100644
--- a/src/test/java/org/apache/xml/security/test/dom/keys/DEREncodedKeyValueTest.java
+++ b/src/test/java/org/apache/xml/security/test/dom/keys/DEREncodedKeyValueTest.java
@@ -20,24 +20,23 @@
import java.nio.file.Files;
import java.nio.file.Path;
-import java.security.KeyFactory;
-import java.security.PublicKey;
+import java.security.*;
import java.security.spec.X509EncodedKeySpec;
import org.apache.xml.security.keys.content.DEREncodedKeyValue;
import org.apache.xml.security.test.XmlSecTestEnvironment;
import org.apache.xml.security.test.dom.TestUtils;
+import org.apache.xml.security.testutils.JDKTestUtils;
import org.apache.xml.security.utils.Constants;
import org.apache.xml.security.utils.XMLUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
-import static org.junit.jupiter.api.Assertions.assertArrayEquals;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.*;
class DEREncodedKeyValueTest {
@@ -47,11 +46,26 @@ class DEREncodedKeyValueTest {
private final PublicKey rsaKeyControl;
private final PublicKey dsaKeyControl;
private final PublicKey ecKeyControl;
+ private final PublicKey edecKeyControl;
+ private final PublicKey xecKeyControl;
+ private final PublicKey dhKeyControl;
+ private final PublicKey rsaSsaPssKeyControl;
public DEREncodedKeyValueTest() throws Exception {
rsaKeyControl = loadPublicKey("rsa.key", "RSA");
dsaKeyControl = loadPublicKey("dsa.key", "DSA");
ecKeyControl = loadPublicKey("ec.key", "EC");
+ edecKeyControl = loadPublicKey("ed25519.key", "EdDSA");
+ xecKeyControl = loadPublicKey("x25519.key", "XDH");
+ dhKeyControl = loadPublicKey("dh.key", "DiffieHellman");
+ rsaSsaPssKeyControl = loadPublicKey("rsassa-pss.key", "RSASSA-PSS");
+ }
+
+ @AfterEach
+ void cleanup() {
+ if (JDKTestUtils.isAuxiliaryProviderRegistered()) {
+ JDKTestUtils.unregisterAuxiliaryProvider();
+ }
}
@Test
@@ -99,6 +113,66 @@ void testECPublicKeyFromElement() throws Exception {
assertEquals(ID_CONTROL, derEncodedKeyValue.getId());
}
+ @Test
+ void testEdECPublicKeyFromElement() throws Exception {
+ Assumptions.assumeTrue(edecKeyControl != null, "Skip tests since EdEC Key is not supported");
+ if (!JDKTestUtils.isAlgorithmSupportedByJDK(edecKeyControl.getAlgorithm())) {
+ JDKTestUtils.registerAuxiliaryProvider();
+ }
+
+ Document doc = loadXML("DEREncodedKeyValue-EdEC.xml");
+ NodeList nl = doc.getElementsByTagNameNS(Constants.SignatureSpec11NS, Constants._TAG_DERENCODEDKEYVALUE);
+ Element element = (Element) nl.item(0);
+
+ DEREncodedKeyValue derEncodedKeyValue = new DEREncodedKeyValue(element, "");
+ assertEquals(edecKeyControl, derEncodedKeyValue.getPublicKey());
+ assertArrayEquals(edecKeyControl.getEncoded(), derEncodedKeyValue.getBytesFromTextChild());
+ assertEquals(ID_CONTROL, derEncodedKeyValue.getId());
+ }
+
+ @Test
+ void testXECPublicKeyFromElement() throws Exception {
+ Assumptions.assumeTrue(xecKeyControl != null, "Skip tests since XEC Key is not supported");
+ String algorithm = xecKeyControl.getAlgorithm();
+ if (!JDKTestUtils.isAlgorithmSupportedByJDK(algorithm)) {
+ JDKTestUtils.registerAuxiliaryProvider();
+ }
+
+ Document doc = loadXML("DEREncodedKeyValue-XEC.xml");
+ NodeList nl = doc.getElementsByTagNameNS(Constants.SignatureSpec11NS, Constants._TAG_DERENCODEDKEYVALUE);
+ Element element = (Element) nl.item(0);
+
+ DEREncodedKeyValue derEncodedKeyValue = new DEREncodedKeyValue(element, "");
+ assertNotNull(xecKeyControl.getAlgorithm(), derEncodedKeyValue.getPublicKey().getAlgorithm());
+ assertEquals(ID_CONTROL, derEncodedKeyValue.getId());
+ }
+
+ @Test
+ void testDiffieHellmanPublicKeyFromElement() throws Exception {
+ Assumptions.assumeTrue(dhKeyControl != null, "Skip tests since DH Key is not supported");
+
+ Document doc = loadXML("DEREncodedKeyValue-DH.xml");
+ NodeList nl = doc.getElementsByTagNameNS(Constants.SignatureSpec11NS, Constants._TAG_DERENCODEDKEYVALUE);
+ Element element = (Element) nl.item(0);
+
+ DEREncodedKeyValue derEncodedKeyValue = new DEREncodedKeyValue(element, "");
+ assertNotNull(dhKeyControl.getAlgorithm(), derEncodedKeyValue.getPublicKey().getAlgorithm());
+ assertEquals(ID_CONTROL, derEncodedKeyValue.getId());
+ }
+
+ @Test
+ void testRsaSsaPssKeyControlPublicKeyFromElement() throws Exception {
+ Assumptions.assumeTrue(rsaSsaPssKeyControl != null, "Skip tests since RSASSA-PSS Key is not supported");
+
+ Document doc = loadXML("DEREncodedKeyValue-RSASSA-PSS.xml");
+ NodeList nl = doc.getElementsByTagNameNS(Constants.SignatureSpec11NS, Constants._TAG_DERENCODEDKEYVALUE);
+ Element element = (Element) nl.item(0);
+
+ DEREncodedKeyValue derEncodedKeyValue = new DEREncodedKeyValue(element, "");
+ assertNotNull(rsaSsaPssKeyControl.getAlgorithm(), derEncodedKeyValue.getPublicKey().getAlgorithm());
+ assertEquals(ID_CONTROL, derEncodedKeyValue.getId());
+ }
+
@Test
void testRSAPublicKeyFromKey() throws Exception {
DEREncodedKeyValue derEncodedKeyValue = new DEREncodedKeyValue(TestUtils.newDocument(), rsaKeyControl);
@@ -120,6 +194,29 @@ void testECPublicKeyFromKey() throws Exception {
assertArrayEquals(ecKeyControl.getEncoded(), derEncodedKeyValue.getBytesFromTextChild());
}
+ @Test
+ void testEdECPublicKeyFromKey() throws Exception {
+ Assumptions.assumeTrue(edecKeyControl != null, "Skip tests since EdEC Key is not supported");
+ if (!JDKTestUtils.isAlgorithmSupportedByJDK(edecKeyControl.getAlgorithm())) {
+ JDKTestUtils.registerAuxiliaryProvider();
+ }
+
+ DEREncodedKeyValue derEncodedKeyValue = new DEREncodedKeyValue(TestUtils.newDocument(), edecKeyControl);
+ assertEquals(edecKeyControl, derEncodedKeyValue.getPublicKey());
+ assertArrayEquals(edecKeyControl.getEncoded(), derEncodedKeyValue.getBytesFromTextChild());
+ }
+
+ @Test
+ void testXECPublicKeyFromKey() throws Exception {
+ Assumptions.assumeTrue(xecKeyControl != null, "Skip tests since XEC Key is not supported");
+ if (!JDKTestUtils.isAlgorithmSupportedByJDK(xecKeyControl.getAlgorithm())) {
+ JDKTestUtils.registerAuxiliaryProvider();
+ }
+
+ DEREncodedKeyValue derEncodedKeyValue = new DEREncodedKeyValue(TestUtils.newDocument(), xecKeyControl);
+ assertArrayEquals(xecKeyControl.getEncoded(), derEncodedKeyValue.getBytesFromTextChild());
+ }
+
@Test
void testId() throws Exception {
DEREncodedKeyValue derEncodedKeyValue = new DEREncodedKeyValue(TestUtils.newDocument(), rsaKeyControl);
@@ -149,9 +246,14 @@ private Document loadXML(String fileName) throws Exception {
private PublicKey loadPublicKey(String fileName, String algorithm) throws Exception {
String fileData = Files.readString(getControlFilePath(fileName));
byte[] keyBytes = XMLUtils.decode(fileData);
- KeyFactory kf = KeyFactory.getInstance(algorithm);
+ if (!JDKTestUtils.isAlgorithmSupported(algorithm, true)) {
+ return null;
+ }
+ Provider provider = JDKTestUtils.isAlgorithmSupportedByJDK(algorithm) ? null : JDKTestUtils.getAuxiliaryProvider();
+
+ KeyFactory kf = provider == null?
+ KeyFactory.getInstance(algorithm) : KeyFactory.getInstance(algorithm, provider);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
return kf.generatePublic(keySpec);
}
-
-}
\ No newline at end of file
+}
diff --git a/src/test/java/org/apache/xml/security/testutils/JDKTestUtils.java b/src/test/java/org/apache/xml/security/testutils/JDKTestUtils.java
new file mode 100644
index 000000000..f7254b6da
--- /dev/null
+++ b/src/test/java/org/apache/xml/security/testutils/JDKTestUtils.java
@@ -0,0 +1,147 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.xml.security.testutils;
+
+import java.lang.System.Logger.Level;
+import java.lang.reflect.Constructor;
+import java.security.Provider;
+import java.security.Security;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+
+/**
+ * The class provides testing utility methods to test XMLSEC functionality with various JDK version. Where possible
+ * we use JDK provided algorithm implementations. However, some algorithms are not supported in lower JDK versions. For example
+ * XDH keys were supported from JDK 11, EdDSA keys from JDK 16, etc. To ensure tests are executed for various JDK versions,
+ * we need to know which algorithms are supported from particular JDK version.
+ *
+ * If the JDK security providers do not support algorithm, the class provides auxiliary security provider (BouncyCastle) to the test
+ * xmlsec functionality ...
+ *
+ */
+public class JDKTestUtils {
+
+
+ // Purpose of auxiliary security provider is to enable testing of algorithms not supported by default JDK security providers.
+ private static final String TEST_PROVIDER_CLASSNAME_PROPERTY = "test.auxiliary.jce.provider.classname";
+ private static final String TEST_PROVIDER_CLASSNAME_DEFAULT = "org.bouncycastle.jce.provider.BouncyCastleProvider";
+ private static final System.Logger LOG = System.getLogger(JDKTestUtils.class.getName());
+
+ private static Provider auxiliaryProvider;
+ private static boolean auxiliaryProviderInitialized = false;
+ private static SetKEY_RESOURCE_PATH
.
+ */
+ public enum TestKeys {
+ DSA("dsa.key", "DSA", "1.2.840.10040.4.1"),
+ RSA("rsa.key", "RSA", "1.2.840.113549.1.1.1"),
+ EC("ec.key", "EC", "1.2.840.10045.2.1"),
+ X25519("x25519.key", "XDH", "1.3.101.110"),
+ ED25519("ed25519.key", "EdDSA", "1.3.101.112");
+
+ private final String filename;
+ private final String algorithm;
+ private final String oid;
+
+ TestKeys(String filename, String algorithm, String oid) {
+ this.filename = filename;
+ this.algorithm = algorithm;
+ this.oid = oid;
+ }
+
+ public String getFilename() {
+ return filename;
+ }
+
+ public String getAlgorithm() {
+ return algorithm;
+ }
+
+ public String getOid() {
+ return oid;
+ }
+ }
+
+ private static final String KEY_RESOURCE_PATH = "/org/apache/xml/security/keys/content/";
+
+ public static KeyPair generateKeyPair(KeyUtils.KeyType keyType) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidParameterSpecException {
+ String keyAlgorithm = keyType.getAlgorithm().getJceName();
+ Provider provider = JDKTestUtils.isAlgorithmSupportedByJDK(keyAlgorithm) ? null : JDKTestUtils.getAuxiliaryProvider();
+ KeyPairGenerator keyPairGenerator;
+
+ switch (keyType.getAlgorithm()){
+ case EC:{
+ keyPairGenerator = provider == null ? KeyPairGenerator.getInstance(keyAlgorithm) :
+ KeyPairGenerator.getInstance(keyAlgorithm, provider);
+ ECGenParameterSpec kpgparams = new ECGenParameterSpec(keyType.getName());
+ keyPairGenerator.initialize(kpgparams);
+ break;
+ }
+ case DSA:
+ case RSA:
+ case RSASSA_PSS:
+ case EdDSA:
+ case DH:
+ case XDH:{
+ keyPairGenerator = provider == null ? KeyPairGenerator.getInstance(keyType.getName()) :
+ KeyPairGenerator.getInstance(keyType.getName(), provider);
+ break;
+ }
+ default:
+ throw new IllegalStateException("Unexpected value: " + keyAlgorithm);
+
+ }
+ return keyPairGenerator.generateKeyPair();
+ }
+
+ public static KeyPair generateKeyPairIfSupported(KeyUtils.KeyType keyType){
+ KeyPair keyPair = null;
+ try {
+ keyPair = generateKeyPair(keyType);
+ } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | InvalidParameterSpecException e) {
+ LOG.log(DEBUG, "Key algorithm [{0}] is not supported! Error message: [{1}]", keyType, e.getMessage());
+ }
+ return keyPair;
+ }
+
+ public static PublicKey loadPublicKey(String keyName, String algorithm) throws Exception {
+ byte[] keyBytes = getKeyResourceAsByteArray(keyName);
+ KeyFactory kf = JDKTestUtils.isAlgorithmSupportedByJDK(algorithm) ?
+ KeyFactory.getInstance(algorithm) : KeyFactory.getInstance(algorithm, JDKTestUtils.getAuxiliaryProvider());
+ X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
+ return kf.generatePublic(keySpec);
+ }
+
+ public static byte[] getKeyResourceAsByteArray(String fileName) throws IOException {
+ byte[] keyBytes;
+ try (InputStream keyIS = getKeyResourceAsInputStream(fileName)){
+ keyBytes = new byte[keyIS.available()];
+ keyIS.read(keyBytes);
+ }
+ return Base64.getMimeDecoder().decode(keyBytes);
+ }
+
+ public static InputStream getKeyResourceAsInputStream(String fileName) {
+ return KeyTestUtils.class.getResourceAsStream(KEY_RESOURCE_PATH + fileName);
+ }
+}
diff --git a/src/test/java/org/apache/xml/security/utils/DERDecoderUtilsTest.java b/src/test/java/org/apache/xml/security/utils/DERDecoderUtilsTest.java
new file mode 100644
index 000000000..69097ecc2
--- /dev/null
+++ b/src/test/java/org/apache/xml/security/utils/DERDecoderUtilsTest.java
@@ -0,0 +1,92 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *