[java-opensaml] 18/19: Add key agreement support to EncryptionParametersResolver impls

Brent Putman putmanb at georgetown.edu
Mon Mar 1 05:56:55 UTC 2021


This is an automated email from the git hooks/post-receive script.

putmanb pushed a commit to branch dev/OSJ-82
in repository java-opensaml.

View the commit online:
http://git.shibboleth.net/view/?p=java-opensaml.git;a=commit;h=4dc579bb622533dfefe0390666bbbdc670e6519e

commit 4dc579bb622533dfefe0390666bbbdc670e6519e
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Fri Feb 19 01:46:13 2021 -0500

    Add key agreement support to EncryptionParametersResolver impls
---
 ...etadataKeyAgreementEncryptionConfiguration.java |  77 +++++
 .../org/opensaml/saml/security/package-info.java   |  22 ++
 .../SAMLMetadataEncryptionParametersResolver.java  | 208 +++++++++++-
 .../saml/common/testing/SAMLTestSupport.java       |   2 +
 ...MLMetadataEncryptionParametersResolverTest.java | 363 ++++++++++++++++++++-
 .../src/test/resources/logback-test.xml            |   8 +
 .../opensaml/xmlsec/EncryptionConfiguration.java   |  10 +
 .../xmlsec/agreement/KeyAgreementSupport.java      |  49 +++
 .../xmlsec/algorithm/AlgorithmSupport.java         |  34 ++
 .../KeyAgreementEncryptionConfiguration.java       |  85 +++++
 .../xmlsec/algorithm/AlgorithmSupportTest.java     |  31 ++
 .../DefaultSecurityConfigurationBootstrap.java     |  37 ++-
 .../xmlsec/impl/BasicEncryptionConfiguration.java  |  24 ++
 .../impl/BasicEncryptionParametersResolver.java    | 162 ++++++++-
 .../BasicSignatureSigningParametersResolver.java   |   3 +-
 .../BasicEncryptionParametersResolverTest.java     | 200 +++++++++++-
 16 files changed, 1294 insertions(+), 21 deletions(-)

diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/security/SAMLMetadataKeyAgreementEncryptionConfiguration.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/security/SAMLMetadataKeyAgreementEncryptionConfiguration.java
new file mode 100644
index 000000000..9bd85f01b
--- /dev/null
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/security/SAMLMetadataKeyAgreementEncryptionConfiguration.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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.opensaml.saml.security;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.saml.saml2.metadata.EncryptionMethod;
+import org.opensaml.saml.saml2.metadata.KeyDescriptor;
+import org.opensaml.xmlsec.encryption.support.KeyAgreementEncryptionConfiguration;
+
+/**
+ * A specialization of {@link KeyAgreementEncryptionConfiguration} that can hold configuration
+ * specific to the user of SAML metadata.
+ */
+public class SAMLMetadataKeyAgreementEncryptionConfiguration extends KeyAgreementEncryptionConfiguration {
+    
+    /** Options for whether to use symmetric key wrap with credentials from SAML metadata. */
+    public enum KeyWrap {
+        
+        /** Always use key wrap for metadata credentials. */
+        Always,
+        
+        /** Never use key wrap for metadata credentials. */
+        Never,
+        
+        /** Use key wrap if no indication is given via {@link EncryptionMethod} elements within
+         * the associated {@link KeyDescriptor} element. See also {@link #Default}. */
+        IfNotIndicated,
+        
+        /** Default behavior, which is to enable key wrap or not based on the presence or absence respectively of
+         * {@link EncryptionMethod} elements in the associated {@link KeyDescriptor} containing 
+         * symmetric key wrap algorithms.
+         * The presence of any symmetric key wrap algorithms (after runtime support and include/exclude filtering)
+         * will enable key wrap. Otherwise, key wrap will be disabled.
+         */
+        Default;
+    }
+    
+    /** Option which determines whether symmetric key wrap is to be used with metadata credentials. */
+    private KeyWrap metadataUseKeyWrap;
+    
+    /**
+     * Get the option which determines whether symmetric key wrap is to be used with metadata credentials.
+     * 
+     * @return the configured optiona value, or null if not explicitly configured
+     */
+    @Nullable public KeyWrap getMetadataUseKeyWrap() {
+        return metadataUseKeyWrap;
+    }
+    
+    /**
+     * Set the option which determines whether symmetric key wrap is to be used with metadata credentials.
+     * 
+     * @param option the new option value
+     */
+    public void setMetadataUseKeyWrap(@Nullable final KeyWrap option) {
+        metadataUseKeyWrap = option;
+    }
+    
+}
+
+
diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/security/package-info.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/security/package-info.java
new file mode 100644
index 000000000..ffecede7d
--- /dev/null
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/security/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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.
+ */
+
+/**
+ * Classes related to general security components within a SAML system.
+ */
+
+package org.opensaml.saml.security;
\ No newline at end of file
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/security/impl/SAMLMetadataEncryptionParametersResolver.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/security/impl/SAMLMetadataEncryptionParametersResolver.java
index 4fc503bb6..5f71be2e1 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/security/impl/SAMLMetadataEncryptionParametersResolver.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/security/impl/SAMLMetadataEncryptionParametersResolver.java
@@ -18,8 +18,12 @@
 package org.opensaml.saml.security.impl;
 
 import java.security.Key;
+import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -28,23 +32,31 @@ import net.shibboleth.utilities.java.support.annotation.ParameterName;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.collection.Pair;
 import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.logic.PredicateSupport;
 import net.shibboleth.utilities.java.support.primitive.StringSupport;
 import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
 import net.shibboleth.utilities.java.support.resolver.ResolverException;
 
+import org.bouncycastle.jcajce.spec.UserKeyingMaterialSpec;
 import org.opensaml.core.xml.XMLObject;
 import org.opensaml.saml.saml2.metadata.EncryptionMethod;
+import org.opensaml.saml.security.SAMLMetadataKeyAgreementEncryptionConfiguration;
+import org.opensaml.saml.security.SAMLMetadataKeyAgreementEncryptionConfiguration.KeyWrap;
 import org.opensaml.security.credential.Credential;
 import org.opensaml.security.credential.CredentialSupport;
 import org.opensaml.security.credential.UsageType;
 import org.opensaml.security.criteria.UsageCriterion;
 import org.opensaml.security.crypto.KeySupport;
+import org.opensaml.xmlsec.EncryptionConfiguration;
 import org.opensaml.xmlsec.EncryptionParameters;
 import org.opensaml.xmlsec.KeyTransportAlgorithmPredicate;
+import org.opensaml.xmlsec.agreement.KeyAgreementSupport;
 import org.opensaml.xmlsec.algorithm.AlgorithmSupport;
+import org.opensaml.xmlsec.criterion.EncryptionConfigurationCriterion;
 import org.opensaml.xmlsec.encryption.MGF;
 import org.opensaml.xmlsec.encryption.OAEPparams;
 import org.opensaml.xmlsec.encryption.support.EncryptionConstants;
+import org.opensaml.xmlsec.encryption.support.KeyAgreementEncryptionConfiguration;
 import org.opensaml.xmlsec.encryption.support.RSAOAEPParameters;
 import org.opensaml.xmlsec.impl.BasicEncryptionParametersResolver;
 import org.opensaml.xmlsec.signature.DigestMethod;
@@ -82,6 +94,9 @@ public class SAMLMetadataEncryptionParametersResolver extends BasicEncryptionPar
      */
     private boolean mergeMetadataRSAOAEPParametersWithConfig;
     
+    /** Default for usage of key wrapping with key agreement if not otherwise configured. */
+    @Nonnull private KeyWrap defaultKeyAgreementUseKeyWrap = KeyWrap.Default;
+    
     /**
      * Constructor.
      *
@@ -117,6 +132,36 @@ public class SAMLMetadataEncryptionParametersResolver extends BasicEncryptionPar
     public void setMergeMetadataRSAOAEPParametersWithConfig(final boolean flag) {
         mergeMetadataRSAOAEPParametersWithConfig = flag;
     }
+    
+    /**
+     * Get the default for usage of key wrapping with key agreement if not otherwise configured.
+     * 
+     * <p>
+     * The default is: {@link KeyWrap#Default}.
+     * </p>
+     * 
+     * @return the default value
+     */
+    @Nonnull public KeyWrap getDefaultKeyAgreemenUseKeyWrap() {
+        return defaultKeyAgreementUseKeyWrap;
+    }
+    
+    /**
+     * Set the default for usage of key wrapping with key agreement if not otherwise configured.
+     * 
+     * <p>
+     * The default is: {@link KeyWrap#Default}.
+     * </p>
+     * 
+     * @param keyWrap the value to set; null implies {@link KeyWrap#Default}
+     */
+    public void setDefaultKeyAgreementUseKeyWrap(@Nullable final KeyWrap keyWrap) {
+        if (keyWrap == null) {
+            defaultKeyAgreementUseKeyWrap = KeyWrap.Default;
+        } else {
+            defaultKeyAgreementUseKeyWrap = keyWrap;
+        }
+    }
 
     /**
      * Get the metadata credential resolver instance to use to resolve encryption credentials.
@@ -138,36 +183,41 @@ public class SAMLMetadataEncryptionParametersResolver extends BasicEncryptionPar
         mdCredResolverCriteria.addAll(criteria);
         mdCredResolverCriteria.add(new UsageCriterion(UsageType.ENCRYPTION), true);
         
-        // Note: Here we assume that we will only ever resolve a key transport credential from metadata.
-        // Even if it's a symmetric key credential (via a key agreement protocol, or resolved from a KeyName, etc),
-        // it ought to be used for symmetric key wrap, not direct data encryption.
+        // Note: Here we primarily assume that we will resolve a key transport credential from metadata.
+        // Even if it's a symmetric key credential (e.g. resolved from a KeyName, etc),
+        // it will be used for symmetric key wrap, not direct data encryption.
+        // The exception is key agreement (e.g. ECDH), which is handled as a special case and may be
+        // either, determined by both metadata and local configuration.
         try {
-            for (final Credential keyTransportCredential :
-                            getMetadataCredentialResolver().resolve(mdCredResolverCriteria)) {
+            for (final Credential credential : getMetadataCredentialResolver().resolve(mdCredResolverCriteria)) {
                 
                 if (log.isTraceEnabled()) {
-                    final Key key = CredentialSupport.extractEncryptionKey(keyTransportCredential);
-                    log.trace("Evaluating key transport encryption credential from SAML metadata of type: {}", 
+                    final Key key = CredentialSupport.extractEncryptionKey(credential);
+                    log.trace("Evaluating candidate encryption credential from SAML metadata of type: {}", 
                             key != null ? key.getAlgorithm() : "n/a");
                 }
                 
+                if (checkAndProcessKeyAgreement(params, criteria, whitelistBlacklistPredicate, credential)) {
+                    return;
+                }
+                
                 final SAMLMDCredentialContext metadataCredContext = 
-                        keyTransportCredential.getCredentialContextSet().get(SAMLMDCredentialContext.class);
+                        credential.getCredentialContextSet().get(SAMLMDCredentialContext.class);
                 
                 final Pair<String,EncryptionMethod> dataEncryptionAlgorithmAndMethod = resolveDataEncryptionAlgorithm(
                         criteria, whitelistBlacklistPredicate, metadataCredContext);
                 
                 final Pair<String,EncryptionMethod> keyTransportAlgorithmAndMethod = resolveKeyTransportAlgorithm(
-                        keyTransportCredential, criteria, whitelistBlacklistPredicate, 
+                        credential, criteria, whitelistBlacklistPredicate, 
                         dataEncryptionAlgorithmAndMethod.getFirst(), metadataCredContext);
                 if (keyTransportAlgorithmAndMethod.getFirst() == null) {
                     log.debug("Unable to resolve key transport algorithm for credential with key type '{}', " 
                             + "considering other credentials", 
-                            CredentialSupport.extractEncryptionKey(keyTransportCredential).getAlgorithm());
+                            CredentialSupport.extractEncryptionKey(credential).getAlgorithm());
                     continue;
                 }
                 
-                params.setKeyTransportEncryptionCredential(keyTransportCredential);
+                params.setKeyTransportEncryptionCredential(credential);
                 params.setKeyTransportEncryptionAlgorithm(keyTransportAlgorithmAndMethod.getFirst());
                 params.setDataEncryptionAlgorithm(dataEncryptionAlgorithmAndMethod.getFirst());
                 
@@ -187,7 +237,141 @@ public class SAMLMetadataEncryptionParametersResolver extends BasicEncryptionPar
         
         super.resolveAndPopulateCredentialsAndAlgorithms(params, criteria, whitelistBlacklistPredicate);
     }
-
+    
+    /**
+     * 
+     * Check for a credential type that implies a key agreement operation, and process if so indicated.
+     * 
+     * @param params the params instance being populated
+     * @param criteria the input criteria being evaluated
+     * @param whitelistBlacklistPredicate the whitelist/blacklist predicate
+     * @param credential the credential being evaluated
+     * 
+     * @return true if all required parameters were supplied, key agreement was successfully performed,
+     *         and the {@link EncryptionParameters} instance's credential and algorithms properties are fully populated,
+     *         otherwise false
+     */
+    protected boolean checkAndProcessKeyAgreement(@Nonnull final EncryptionParameters params,
+            @Nonnull final CriteriaSet criteria, @Nonnull final Predicate<String> whitelistBlacklistPredicate,
+            @Nonnull final Credential credential) {
+        
+        if (!KeyAgreementSupport.supportsKeyAgreement(credential) ) {
+            log.trace("Specified Credential does not support key agreement");
+            return false; 
+        }
+        
+        final SAMLMetadataKeyAgreementEncryptionConfiguration config =
+                getEffectiveKeyAgreementConfiguration(criteria, credential);
+        if (config == null) {
+            log.warn("Unable to get effective KeyAgreementEncryptionConfiguration for credential with key type: {}",
+                    credential.getPublicKey().getAlgorithm());
+            return false;
+        }
+        
+        final List<String> criteriaKeyTransportAlgorithms = getEffectiveKeyTransportAlgorithms(criteria,
+                whitelistBlacklistPredicate);
+        
+        final List<String> criteriaDataEncryptionAlgorithms = getEffectiveDataEncryptionAlgorithms(criteria, 
+                whitelistBlacklistPredicate);
+        
+        final SAMLMDCredentialContext metadataCredContext = 
+                credential.getCredentialContextSet().get(SAMLMDCredentialContext.class);
+        
+        List<String> metadataKeyWrapAlgorithms = Collections.emptyList();
+        List<String> metadataDataEncryptionAlgorithms = Collections.emptyList(); 
+        if (metadataCredContext != null) {
+            final List<String> metadataAlgorithms = metadataCredContext.getEncryptionMethods().stream()
+                    .map(EncryptionMethod::getAlgorithm)
+                    .filter(Objects::nonNull)
+                    .filter(PredicateSupport.and(getAlgorithmRuntimeSupportedPredicate(), whitelistBlacklistPredicate))
+                    .collect(Collectors.toList());
+            
+            metadataKeyWrapAlgorithms = metadataAlgorithms.stream()
+                    .filter(AlgorithmSupport::isSymmetricKeyWrap)
+                    .collect(Collectors.toList());
+            
+            metadataDataEncryptionAlgorithms = metadataAlgorithms.stream()
+                    .filter(AlgorithmSupport::isBlockEncryption)
+                    .collect(Collectors.toList());
+        }
+        
+        log.debug("Evaling useKeyWrap: # key wrap algos '{}', # direct data algos '{}', config '{}'",
+                metadataKeyWrapAlgorithms.size(), metadataDataEncryptionAlgorithms.size(),
+                config.getMetadataUseKeyWrap());
+        
+        boolean useKeyWrap = false;
+        if (KeyWrap.Never == config.getMetadataUseKeyWrap()) {
+            useKeyWrap = false;
+        } else if (KeyWrap.Always == config.getMetadataUseKeyWrap() || !metadataKeyWrapAlgorithms.isEmpty()) {
+            useKeyWrap = true;
+        } else {
+            useKeyWrap = metadataDataEncryptionAlgorithms.isEmpty()
+                    && KeyWrap.IfNotIndicated == config.getMetadataUseKeyWrap();
+        }
+        
+        return checkAndProcessKeyAgreement(params, criteria, credential,
+                concatLists(metadataDataEncryptionAlgorithms, criteriaDataEncryptionAlgorithms),
+                useKeyWrap ? concatLists(metadataKeyWrapAlgorithms, criteriaKeyTransportAlgorithms)
+                        : Collections.emptyList());
+    }
+    
+    /**
+     * Get the effective {@link SAMLMetadataKeyAgreementEncryptionConfiguration} to use with the specified credential.
+     * 
+     * @param criteria the criteria
+     * @param credential the credential to evaluate
+     * @return the key agreement configuration for the credential, or null if could not be resolved
+     */
+    @Nullable protected SAMLMetadataKeyAgreementEncryptionConfiguration getEffectiveKeyAgreementConfiguration(
+            @Nonnull final CriteriaSet criteria, @Nonnull final Credential credential) {
+        
+        final KeyAgreementEncryptionConfiguration baseConfig =
+                super.getEffectiveKeyAgreementConfiguration(criteria, credential);
+        if (baseConfig == null) {
+            return null;
+        }
+        
+        final SAMLMetadataKeyAgreementEncryptionConfiguration config =
+                new SAMLMetadataKeyAgreementEncryptionConfiguration();
+        
+        config.setAlgorithm(baseConfig.getAlgorithm());
+        config.setParameters(baseConfig.getParameters());
+        
+        final String keyType = credential.getPublicKey().getAlgorithm();
+        
+        final List<EncryptionConfiguration> encConfigs = criteria.get(EncryptionConfigurationCriterion.class)
+                .getConfigurations();
+        
+        config.setMetadataUseKeyWrap(
+                encConfigs.stream()
+                    .map(c -> c.getKeyAgreementConfigurations().get(keyType))
+                    .filter(Objects::nonNull)
+                    .filter(SAMLMetadataKeyAgreementEncryptionConfiguration.class::isInstance)
+                    .map(SAMLMetadataKeyAgreementEncryptionConfiguration.class::cast)
+                    .map(SAMLMetadataKeyAgreementEncryptionConfiguration::getMetadataUseKeyWrap)
+                    .filter(Objects::nonNull)
+                    .findFirst().orElse(getDefaultKeyAgreemenUseKeyWrap())
+                );
+        
+        return config;
+        
+    }
+    
+    /**
+     * Concatenate multiple lists into one list.
+     * 
+     * @param lists the lists to process
+     * 
+     * @return the concatenation of the supplied lists
+     */
+    @SafeVarargs
+    private List<String> concatLists(@Nonnull final List<String> ... lists) {
+        return Stream.of(lists)
+                .filter(Objects::nonNull)
+                .flatMap(x -> x.stream())
+                .collect(Collectors.toList());
+    }
+    
     /**
      * Resolve and populate an instance of {@link RSAOAEPParameters}, if appropriate for the selected
      * key transport encryption algorithm.
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/testing/SAMLTestSupport.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/testing/SAMLTestSupport.java
index 5364d877e..255209dea 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/testing/SAMLTestSupport.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/testing/SAMLTestSupport.java
@@ -24,6 +24,7 @@ import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
 import org.opensaml.xmlsec.keyinfo.impl.BasicProviderKeyInfoCredentialResolver;
 import org.opensaml.xmlsec.keyinfo.impl.KeyInfoProvider;
 import org.opensaml.xmlsec.keyinfo.impl.provider.DSAKeyValueProvider;
+import org.opensaml.xmlsec.keyinfo.impl.provider.ECKeyValueProvider;
 import org.opensaml.xmlsec.keyinfo.impl.provider.InlineX509DataProvider;
 import org.opensaml.xmlsec.keyinfo.impl.provider.RSAKeyValueProvider;
 
@@ -45,6 +46,7 @@ public final class SAMLTestSupport {
         List<KeyInfoProvider> providers = new ArrayList<>();
         providers.add( new RSAKeyValueProvider() );
         providers.add( new DSAKeyValueProvider() );
+        providers.add( new ECKeyValueProvider() );
         providers.add( new InlineX509DataProvider() );
         return new BasicProviderKeyInfoCredentialResolver(providers);
     }
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/security/impl/SAMLMetadataEncryptionParametersResolverTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/security/impl/SAMLMetadataEncryptionParametersResolverTest.java
index a4b0e3811..654435bcf 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/security/impl/SAMLMetadataEncryptionParametersResolverTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/security/impl/SAMLMetadataEncryptionParametersResolverTest.java
@@ -17,18 +17,21 @@
 
 package org.opensaml.saml.security.impl;
 
+import java.security.InvalidAlgorithmParameterException;
 import java.security.KeyPair;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.PublicKey;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
+import java.security.spec.ECGenParameterSpec;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.annotation.Nullable;
 
@@ -48,6 +51,8 @@ import org.opensaml.saml.saml2.metadata.EntityDescriptor;
 import org.opensaml.saml.saml2.metadata.KeyDescriptor;
 import org.opensaml.saml.saml2.metadata.RoleDescriptor;
 import org.opensaml.saml.saml2.metadata.SPSSODescriptor;
+import org.opensaml.saml.security.SAMLMetadataKeyAgreementEncryptionConfiguration;
+import org.opensaml.saml.security.SAMLMetadataKeyAgreementEncryptionConfiguration.KeyWrap;
 import org.opensaml.security.credential.Credential;
 import org.opensaml.security.credential.CredentialSupport;
 import org.opensaml.security.credential.UsageType;
@@ -56,20 +61,26 @@ import org.opensaml.security.crypto.KeySupport;
 import org.opensaml.security.testing.SecurityProviderTestSupport;
 import org.opensaml.xmlsec.EncryptionParameters;
 import org.opensaml.xmlsec.KeyTransportAlgorithmPredicate;
+import org.opensaml.xmlsec.agreement.KeyAgreementCredential;
 import org.opensaml.xmlsec.algorithm.AlgorithmRegistry;
 import org.opensaml.xmlsec.algorithm.AlgorithmSupport;
 import org.opensaml.xmlsec.config.GlobalAlgorithmRegistryInitializer;
 import org.opensaml.xmlsec.criterion.EncryptionConfigurationCriterion;
 import org.opensaml.xmlsec.criterion.KeyInfoGenerationProfileCriterion;
+import org.opensaml.xmlsec.derivation.impl.ConcatKDF;
+import org.opensaml.xmlsec.derivation.impl.PBKDF2;
 import org.opensaml.xmlsec.encryption.MGF;
 import org.opensaml.xmlsec.encryption.OAEPparams;
 import org.opensaml.xmlsec.encryption.support.EncryptionConstants;
+import org.opensaml.xmlsec.encryption.support.KeyAgreementEncryptionConfiguration;
 import org.opensaml.xmlsec.encryption.support.RSAOAEPParameters;
 import org.opensaml.xmlsec.impl.BasicEncryptionConfiguration;
 import org.opensaml.xmlsec.keyinfo.KeyInfoSupport;
 import org.opensaml.xmlsec.keyinfo.NamedKeyInfoGeneratorManager;
 import org.opensaml.xmlsec.keyinfo.impl.BasicKeyInfoGeneratorFactory;
+import org.opensaml.xmlsec.keyinfo.impl.KeyAgreementKeyInfoGeneratorFactory;
 import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory;
+import org.opensaml.xmlsec.keyinfo.impl.KeyAgreementKeyInfoGeneratorFactory.KeyAgreementKeyInfoGenerator;
 import org.opensaml.xmlsec.signature.DigestMethod;
 import org.opensaml.xmlsec.signature.KeyInfo;
 import org.opensaml.xmlsec.signature.support.SignatureConstants;
@@ -96,6 +107,9 @@ public class SAMLMetadataEncryptionParametersResolverTest extends XMLObjectBaseT
     private Credential dsaCred1;
     private String dsaCred1KeyName = "DSACred1";
     
+    private Credential ecCred1;
+    private String ecCred1KeyName = "ECCred1";
+    
     private String defaultRSAKeyTransportAlgo = EncryptionConstants.ALGO_ID_KEYTRANSPORT_RSAOAEP;
     private String defaultAES128DataAlgo = EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128;
     private String defaultAES192DataAlgo = EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES192;
@@ -117,7 +131,7 @@ public class SAMLMetadataEncryptionParametersResolverTest extends XMLObjectBaseT
     }
     
     @BeforeClass
-    public void buildCredentials() throws NoSuchAlgorithmException, NoSuchProviderException {
+    public void buildCredentials() throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
         KeyPair rsaKeyPair = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_RSA, 2048, null);
         rsaCred1 = CredentialSupport.getSimpleCredential(rsaKeyPair.getPublic(), null);
         rsaCred1.getKeyNames().add(rsaCred1KeyName);
@@ -125,6 +139,10 @@ public class SAMLMetadataEncryptionParametersResolverTest extends XMLObjectBaseT
         KeyPair dsaKeyPair = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_DSA, 1024, null);
         dsaCred1 = CredentialSupport.getSimpleCredential(dsaKeyPair.getPublic(), null);
         dsaCred1.getKeyNames().add(dsaCred1KeyName);
+        
+        KeyPair ecKeyPair = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_EC, new ECGenParameterSpec("secp256r1"), null);
+        ecCred1 = CredentialSupport.getSimpleCredential(ecKeyPair.getPublic(), ecKeyPair.getPrivate());
+        ecCred1.getKeyNames().add(ecCred1KeyName);
     }
     
     @BeforeMethod
@@ -159,18 +177,32 @@ public class SAMLMetadataEncryptionParametersResolverTest extends XMLObjectBaseT
                 EncryptionConstants.ALGO_ID_KEYWRAP_TRIPLEDES
                 ));
         
+        SAMLMetadataKeyAgreementEncryptionConfiguration ecConfig = new SAMLMetadataKeyAgreementEncryptionConfiguration();
+        ecConfig.setMetadataUseKeyWrap(KeyWrap.Default);
+        ecConfig.setAlgorithm(EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES);
+        ConcatKDF concatKDF = new ConcatKDF();
+        concatKDF.setAlgorithmID("00");
+        concatKDF.setPartyUInfo("00");
+        concatKDF.setPartyVInfo("00");
+        ecConfig.setParameters(Set.of(concatKDF));
+        config3.setKeyAgreementConfigurations(Map.of("EC", ecConfig));
+        
         BasicKeyInfoGeneratorFactory basicFactory1 = new BasicKeyInfoGeneratorFactory();
         X509KeyInfoGeneratorFactory x509Factory1 = new X509KeyInfoGeneratorFactory();
+        KeyAgreementKeyInfoGeneratorFactory kaFactory1 = new KeyAgreementKeyInfoGeneratorFactory();
         defaultKeyTransportKeyInfoGeneratorManager = new NamedKeyInfoGeneratorManager();
         defaultKeyTransportKeyInfoGeneratorManager.registerDefaultFactory(basicFactory1);
         defaultKeyTransportKeyInfoGeneratorManager.registerDefaultFactory(x509Factory1);
+        defaultKeyTransportKeyInfoGeneratorManager.registerDefaultFactory(kaFactory1);
         config3.setKeyTransportKeyInfoGeneratorManager(defaultKeyTransportKeyInfoGeneratorManager);
         
         BasicKeyInfoGeneratorFactory basicFactory2 = new BasicKeyInfoGeneratorFactory();
         X509KeyInfoGeneratorFactory x509Factory2 = new X509KeyInfoGeneratorFactory();
+        KeyAgreementKeyInfoGeneratorFactory kaFactory2 = new KeyAgreementKeyInfoGeneratorFactory();
         defaultDataEncryptionKeyInfoGeneratorManager = new NamedKeyInfoGeneratorManager();
         defaultDataEncryptionKeyInfoGeneratorManager.registerDefaultFactory(basicFactory2);
         defaultDataEncryptionKeyInfoGeneratorManager.registerDefaultFactory(x509Factory2);
+        defaultDataEncryptionKeyInfoGeneratorManager.registerDefaultFactory(kaFactory2);
         config3.setDataKeyInfoGeneratorManager(defaultDataEncryptionKeyInfoGeneratorManager);
         
         configCriterion = new EncryptionConfigurationCriterion(config1, config2, config3);
@@ -181,6 +213,7 @@ public class SAMLMetadataEncryptionParametersResolverTest extends XMLObjectBaseT
         criteriaSet = new CriteriaSet(configCriterion, roleDescCriterion);
     }
     
+
     @Test
     public void testBasic() throws ResolverException {
         roleDesc.getKeyDescriptors().add(buildKeyDescriptor(rsaCred1KeyName, UsageType.ENCRYPTION, rsaCred1.getPublicKey()));
@@ -544,6 +577,334 @@ public class SAMLMetadataEncryptionParametersResolverTest extends XMLObjectBaseT
         Assert.assertNull(params.getDataKeyInfoGenerator());
     }
     
+    @Test
+    public void testECDHWithNoEncryptionMethodsAndKeyWrapDefault() throws ResolverException {
+        KeyDescriptor kd = buildKeyDescriptor(ecCred1KeyName, UsageType.ENCRYPTION, ecCred1.getPublicKey());
+        roleDesc.getKeyDescriptors().add(kd);
+        
+        EncryptionParameters params = resolver.resolveSingle(criteriaSet);
+        
+        Assert.assertNotNull(params);
+        Assert.assertNull(params.getKeyTransportEncryptionCredential());
+        Assert.assertNull(params.getKeyTransportEncryptionAlgorithm());
+        Assert.assertNull(params.getKeyTransportKeyInfoGenerator());
+        
+        Assert.assertNotNull(params.getDataEncryptionCredential());
+        Assert.assertTrue(KeyAgreementCredential.class.isInstance(params.getDataEncryptionCredential()));
+        Assert.assertNotNull(params.getDataEncryptionCredential().getSecretKey());
+        Assert.assertEquals(params.getDataEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        Assert.assertEquals(KeySupport.getKeyLength(params.getDataEncryptionCredential().getSecretKey()), Integer.valueOf(128));
+        Assert.assertEquals(params.getDataEncryptionAlgorithm(), defaultAES128DataAlgo);
+        Assert.assertNotNull(params.getDataKeyInfoGenerator());
+        Assert.assertTrue(KeyAgreementKeyInfoGenerator.class.isInstance(params.getDataKeyInfoGenerator()));
+    }
+    
+    @Test
+    public void testECDHWithNoEncryptionMethodsAndKeyWrapAlways() throws ResolverException {
+        KeyDescriptor kd = buildKeyDescriptor(ecCred1KeyName, UsageType.ENCRYPTION, ecCred1.getPublicKey());
+        roleDesc.getKeyDescriptors().add(kd);
+        
+        SAMLMetadataKeyAgreementEncryptionConfiguration ecConfig = new SAMLMetadataKeyAgreementEncryptionConfiguration();
+        ecConfig.setMetadataUseKeyWrap(KeyWrap.Always);
+        config2.setKeyAgreementConfigurations(Map.of("EC", ecConfig));
+        
+        EncryptionParameters params = resolver.resolveSingle(criteriaSet);
+        
+        Assert.assertNotNull(params);
+        Assert.assertNotNull(params.getKeyTransportEncryptionCredential());
+        Assert.assertTrue(KeyAgreementCredential.class.isInstance(params.getKeyTransportEncryptionCredential()));
+        Assert.assertNotNull(params.getKeyTransportEncryptionCredential().getSecretKey());
+        Assert.assertEquals(params.getKeyTransportEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        Assert.assertEquals(KeySupport.getKeyLength(params.getKeyTransportEncryptionCredential().getSecretKey()), Integer.valueOf(128));
+        Assert.assertEquals(params.getKeyTransportEncryptionAlgorithm(), EncryptionConstants.ALGO_ID_KEYWRAP_AES128);
+        Assert.assertNotNull(params.getKeyTransportKeyInfoGenerator());
+        Assert.assertTrue(KeyAgreementKeyInfoGenerator.class.isInstance(params.getKeyTransportKeyInfoGenerator()));
+        
+        Assert.assertNull(params.getDataEncryptionCredential());
+        Assert.assertEquals(params.getDataEncryptionAlgorithm(), defaultAES128DataAlgo);
+        Assert.assertNull(params.getDataKeyInfoGenerator());
+    }
+    
+    @Test
+    public void testECDHWithNoEncryptionMethodsAndKeyWrapNever() throws ResolverException {
+        KeyDescriptor kd = buildKeyDescriptor(ecCred1KeyName, UsageType.ENCRYPTION, ecCred1.getPublicKey());
+        roleDesc.getKeyDescriptors().add(kd);
+        
+        SAMLMetadataKeyAgreementEncryptionConfiguration ecConfig = new SAMLMetadataKeyAgreementEncryptionConfiguration();
+        ecConfig.setMetadataUseKeyWrap(KeyWrap.Never);
+        config2.setKeyAgreementConfigurations(Map.of("EC", ecConfig));
+        
+        EncryptionParameters params = resolver.resolveSingle(criteriaSet);
+        
+        Assert.assertNull(params.getKeyTransportEncryptionCredential());
+        Assert.assertNull(params.getKeyTransportEncryptionAlgorithm());
+        Assert.assertNull(params.getKeyTransportKeyInfoGenerator());
+        
+        Assert.assertNotNull(params.getDataEncryptionCredential());
+        Assert.assertTrue(KeyAgreementCredential.class.isInstance(params.getDataEncryptionCredential()));
+        Assert.assertNotNull(params.getDataEncryptionCredential().getSecretKey());
+        Assert.assertEquals(params.getDataEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        Assert.assertEquals(KeySupport.getKeyLength(params.getDataEncryptionCredential().getSecretKey()), Integer.valueOf(128));
+        Assert.assertEquals(params.getDataEncryptionAlgorithm(), defaultAES128DataAlgo);
+        Assert.assertNotNull(params.getDataKeyInfoGenerator());
+        Assert.assertTrue(KeyAgreementKeyInfoGenerator.class.isInstance(params.getDataKeyInfoGenerator()));
+    }
+    
+    @Test
+    public void testECDHWithNoEncryptionMethodsAndKeyWrapIfNotIndicated() throws ResolverException {
+        KeyDescriptor kd = buildKeyDescriptor(ecCred1KeyName, UsageType.ENCRYPTION, ecCred1.getPublicKey());
+        roleDesc.getKeyDescriptors().add(kd);
+        
+        SAMLMetadataKeyAgreementEncryptionConfiguration ecConfig = new SAMLMetadataKeyAgreementEncryptionConfiguration();
+        ecConfig.setMetadataUseKeyWrap(KeyWrap.IfNotIndicated);
+        config2.setKeyAgreementConfigurations(Map.of("EC", ecConfig));
+        
+        EncryptionParameters params = resolver.resolveSingle(criteriaSet);
+        
+        Assert.assertNotNull(params);
+        Assert.assertNotNull(params.getKeyTransportEncryptionCredential());
+        Assert.assertTrue(KeyAgreementCredential.class.isInstance(params.getKeyTransportEncryptionCredential()));
+        Assert.assertNotNull(params.getKeyTransportEncryptionCredential().getSecretKey());
+        Assert.assertEquals(params.getKeyTransportEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        Assert.assertEquals(KeySupport.getKeyLength(params.getKeyTransportEncryptionCredential().getSecretKey()), Integer.valueOf(128));
+        Assert.assertEquals(params.getKeyTransportEncryptionAlgorithm(), EncryptionConstants.ALGO_ID_KEYWRAP_AES128);
+        Assert.assertNotNull(params.getKeyTransportKeyInfoGenerator());
+        Assert.assertTrue(KeyAgreementKeyInfoGenerator.class.isInstance(params.getKeyTransportKeyInfoGenerator()));
+        
+        Assert.assertNull(params.getDataEncryptionCredential());
+        Assert.assertEquals(params.getDataEncryptionAlgorithm(), defaultAES128DataAlgo);
+        Assert.assertNull(params.getDataKeyInfoGenerator());
+    }
+    
+    @Test
+    public void testECDHWithBlockEncryptionMethodAndKeyWrapDefault() throws ResolverException {
+        KeyDescriptor kd = buildKeyDescriptor(ecCred1KeyName, UsageType.ENCRYPTION, ecCred1.getPublicKey());
+        kd.getEncryptionMethods().add(buildEncryptionMethod(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256_GCM));
+        roleDesc.getKeyDescriptors().add(kd);
+        
+        EncryptionParameters params = resolver.resolveSingle(criteriaSet);
+        
+        Assert.assertNotNull(params);
+        Assert.assertNull(params.getKeyTransportEncryptionCredential());
+        Assert.assertNull(params.getKeyTransportEncryptionAlgorithm());
+        Assert.assertNull(params.getKeyTransportKeyInfoGenerator());
+        
+        Assert.assertNotNull(params.getDataEncryptionCredential());
+        Assert.assertTrue(KeyAgreementCredential.class.isInstance(params.getDataEncryptionCredential()));
+        Assert.assertNotNull(params.getDataEncryptionCredential().getSecretKey());
+        Assert.assertEquals(params.getDataEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        Assert.assertEquals(KeySupport.getKeyLength(params.getDataEncryptionCredential().getSecretKey()), Integer.valueOf(256));
+        Assert.assertEquals(params.getDataEncryptionAlgorithm(), EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256_GCM);
+        Assert.assertNotNull(params.getDataKeyInfoGenerator());
+        Assert.assertTrue(KeyAgreementKeyInfoGenerator.class.isInstance(params.getDataKeyInfoGenerator()));
+    }
+    
+    @Test
+    public void testECDHWithBlockEncryptionMethodAndKeyWrapAlways() throws ResolverException {
+        KeyDescriptor kd = buildKeyDescriptor(ecCred1KeyName, UsageType.ENCRYPTION, ecCred1.getPublicKey());
+        kd.getEncryptionMethods().add(buildEncryptionMethod(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256_GCM));
+        roleDesc.getKeyDescriptors().add(kd);
+        
+        SAMLMetadataKeyAgreementEncryptionConfiguration ecConfig = new SAMLMetadataKeyAgreementEncryptionConfiguration();
+        ecConfig.setMetadataUseKeyWrap(KeyWrap.Always);
+        config2.setKeyAgreementConfigurations(Map.of("EC", ecConfig));
+        
+        EncryptionParameters params = resolver.resolveSingle(criteriaSet);
+        
+        Assert.assertNotNull(params);
+        Assert.assertNotNull(params.getKeyTransportEncryptionCredential());
+        Assert.assertTrue(KeyAgreementCredential.class.isInstance(params.getKeyTransportEncryptionCredential()));
+        Assert.assertNotNull(params.getKeyTransportEncryptionCredential().getSecretKey());
+        Assert.assertEquals(params.getKeyTransportEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        Assert.assertEquals(KeySupport.getKeyLength(params.getKeyTransportEncryptionCredential().getSecretKey()), Integer.valueOf(128));
+        Assert.assertEquals(params.getKeyTransportEncryptionAlgorithm(), EncryptionConstants.ALGO_ID_KEYWRAP_AES128);
+        Assert.assertNotNull(params.getKeyTransportKeyInfoGenerator());
+        Assert.assertTrue(KeyAgreementKeyInfoGenerator.class.isInstance(params.getKeyTransportKeyInfoGenerator()));
+        
+        
+        Assert.assertNull(params.getDataEncryptionCredential());
+        Assert.assertEquals(params.getDataEncryptionAlgorithm(), EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256_GCM);
+        Assert.assertNull(params.getDataKeyInfoGenerator());
+    }
+
+    @Test
+    public void testECDHWithKeyWrapEncryptionMethodAndKeyWrapDefault() throws ResolverException {
+        KeyDescriptor kd = buildKeyDescriptor(ecCred1KeyName, UsageType.ENCRYPTION, ecCred1.getPublicKey());
+        kd.getEncryptionMethods().add(buildEncryptionMethod(EncryptionConstants.ALGO_ID_KEYWRAP_AES256));
+        roleDesc.getKeyDescriptors().add(kd);
+        
+        EncryptionParameters params = resolver.resolveSingle(criteriaSet);
+        
+        Assert.assertNotNull(params.getKeyTransportEncryptionCredential());
+        Assert.assertTrue(KeyAgreementCredential.class.isInstance(params.getKeyTransportEncryptionCredential()));
+        Assert.assertNotNull(params.getKeyTransportEncryptionCredential().getSecretKey());
+        Assert.assertEquals(params.getKeyTransportEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        Assert.assertEquals(KeySupport.getKeyLength(params.getKeyTransportEncryptionCredential().getSecretKey()), Integer.valueOf(256));
+        Assert.assertEquals(params.getKeyTransportEncryptionAlgorithm(), EncryptionConstants.ALGO_ID_KEYWRAP_AES256);
+        Assert.assertNotNull(params.getKeyTransportKeyInfoGenerator());
+        Assert.assertTrue(KeyAgreementKeyInfoGenerator.class.isInstance(params.getKeyTransportKeyInfoGenerator()));
+        
+        Assert.assertNull(params.getDataEncryptionCredential());
+        Assert.assertEquals(params.getDataEncryptionAlgorithm(), defaultAES128DataAlgo);
+        Assert.assertNull(params.getDataKeyInfoGenerator());
+    }
+    
+    @Test
+    public void testECDHWithKeyWrapEncryptionMethodAndKeyWrapNever() throws ResolverException {
+        KeyDescriptor kd = buildKeyDescriptor(ecCred1KeyName, UsageType.ENCRYPTION, ecCred1.getPublicKey());
+        kd.getEncryptionMethods().add(buildEncryptionMethod(EncryptionConstants.ALGO_ID_KEYWRAP_AES256));
+        roleDesc.getKeyDescriptors().add(kd);
+        
+        SAMLMetadataKeyAgreementEncryptionConfiguration ecConfig = new SAMLMetadataKeyAgreementEncryptionConfiguration();
+        ecConfig.setMetadataUseKeyWrap(KeyWrap.Never);
+        config2.setKeyAgreementConfigurations(Map.of("EC", ecConfig));
+        
+        EncryptionParameters params = resolver.resolveSingle(criteriaSet);
+        
+        Assert.assertNull(params.getKeyTransportEncryptionCredential());
+        Assert.assertNull(params.getKeyTransportEncryptionAlgorithm());
+        Assert.assertNull(params.getKeyTransportKeyInfoGenerator());
+        
+        Assert.assertNotNull(params.getDataEncryptionCredential());
+        Assert.assertTrue(KeyAgreementCredential.class.isInstance(params.getDataEncryptionCredential()));
+        Assert.assertNotNull(params.getDataEncryptionCredential().getSecretKey());
+        Assert.assertEquals(params.getDataEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        Assert.assertEquals(KeySupport.getKeyLength(params.getDataEncryptionCredential().getSecretKey()), Integer.valueOf(128));
+        Assert.assertEquals(params.getDataEncryptionAlgorithm(), defaultAES128DataAlgo);
+        Assert.assertNotNull(params.getDataKeyInfoGenerator());
+        Assert.assertTrue(KeyAgreementKeyInfoGenerator.class.isInstance(params.getDataKeyInfoGenerator()));
+    }
+    
+    @Test
+    public void testECDHWithBlockAndKeyWrapEncryptionMethods() throws ResolverException {
+        KeyDescriptor kd = buildKeyDescriptor(ecCred1KeyName, UsageType.ENCRYPTION, ecCred1.getPublicKey());
+        kd.getEncryptionMethods().add(buildEncryptionMethod(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256_GCM));
+        kd.getEncryptionMethods().add(buildEncryptionMethod(EncryptionConstants.ALGO_ID_KEYWRAP_AES256));
+        roleDesc.getKeyDescriptors().add(kd);
+        
+        EncryptionParameters params = resolver.resolveSingle(criteriaSet);
+        
+        Assert.assertNotNull(params.getKeyTransportEncryptionCredential());
+        Assert.assertTrue(KeyAgreementCredential.class.isInstance(params.getKeyTransportEncryptionCredential()));
+        Assert.assertNotNull(params.getKeyTransportEncryptionCredential().getSecretKey());
+        Assert.assertEquals(params.getKeyTransportEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        Assert.assertEquals(KeySupport.getKeyLength(params.getKeyTransportEncryptionCredential().getSecretKey()), Integer.valueOf(256));
+        Assert.assertEquals(params.getKeyTransportEncryptionAlgorithm(), EncryptionConstants.ALGO_ID_KEYWRAP_AES256);
+        Assert.assertNotNull(params.getKeyTransportKeyInfoGenerator());
+        Assert.assertTrue(KeyAgreementKeyInfoGenerator.class.isInstance(params.getKeyTransportKeyInfoGenerator()));
+        
+        Assert.assertNull(params.getDataEncryptionCredential());
+        Assert.assertEquals(params.getDataEncryptionAlgorithm(), EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256_GCM);
+        Assert.assertNull(params.getDataKeyInfoGenerator());
+    }
+    
+    @Test
+    public void testECDHWithKeyWrapEncryptionMethodAndGeneratedDataCredential() throws ResolverException {
+        KeyDescriptor kd = buildKeyDescriptor(ecCred1KeyName, UsageType.ENCRYPTION, ecCred1.getPublicKey());
+        kd.getEncryptionMethods().add(buildEncryptionMethod(EncryptionConstants.ALGO_ID_KEYWRAP_AES256));
+        roleDesc.getKeyDescriptors().add(kd);
+        
+        resolver.setAutoGenerateDataEncryptionCredential(true);
+        
+        EncryptionParameters params = resolver.resolveSingle(criteriaSet);
+        
+        Assert.assertNotNull(params.getKeyTransportEncryptionCredential());
+        Assert.assertTrue(KeyAgreementCredential.class.isInstance(params.getKeyTransportEncryptionCredential()));
+        Assert.assertNotNull(params.getKeyTransportEncryptionCredential().getSecretKey());
+        Assert.assertEquals(params.getKeyTransportEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        Assert.assertEquals(KeySupport.getKeyLength(params.getKeyTransportEncryptionCredential().getSecretKey()), Integer.valueOf(256));
+        Assert.assertEquals(params.getKeyTransportEncryptionAlgorithm(), EncryptionConstants.ALGO_ID_KEYWRAP_AES256);
+        Assert.assertNotNull(params.getKeyTransportKeyInfoGenerator());
+        Assert.assertTrue(KeyAgreementKeyInfoGenerator.class.isInstance(params.getKeyTransportKeyInfoGenerator()));
+        
+        Assert.assertNotNull(params.getDataEncryptionCredential());
+        Assert.assertNotNull(params.getDataEncryptionCredential().getSecretKey());
+        Assert.assertEquals(params.getDataEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        Assert.assertEquals(KeySupport.getKeyLength(params.getDataEncryptionCredential().getSecretKey()), Integer.valueOf(128));
+        Assert.assertEquals(params.getDataEncryptionAlgorithm(), defaultAES128DataAlgo);
+        Assert.assertNotNull(params.getDataKeyInfoGenerator());
+    }
+    
+    @Test
+    public void testECDHWithKDFOverride() throws ResolverException {
+        KeyDescriptor kd = buildKeyDescriptor(ecCred1KeyName, UsageType.ENCRYPTION, ecCred1.getPublicKey());
+        kd.getEncryptionMethods().add(buildEncryptionMethod(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256_GCM));
+        roleDesc.getKeyDescriptors().add(kd);
+        
+        SAMLMetadataKeyAgreementEncryptionConfiguration ecConfig = new SAMLMetadataKeyAgreementEncryptionConfiguration();
+        PBKDF2 kdf = new PBKDF2();
+        ecConfig.setParameters(Set.of(kdf));
+        config2.setKeyAgreementConfigurations(Map.of("EC", ecConfig));
+        
+        EncryptionParameters params = resolver.resolveSingle(criteriaSet);
+        
+        Assert.assertNotNull(params);
+        Assert.assertNull(params.getKeyTransportEncryptionCredential());
+        Assert.assertNull(params.getKeyTransportEncryptionAlgorithm());
+        Assert.assertNull(params.getKeyTransportKeyInfoGenerator());
+        
+        Assert.assertNotNull(params.getDataEncryptionCredential());
+        Assert.assertTrue(KeyAgreementCredential.class.isInstance(params.getDataEncryptionCredential()));
+        Assert.assertNotNull(params.getDataEncryptionCredential().getSecretKey());
+        Assert.assertEquals(params.getDataEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        Assert.assertEquals(KeySupport.getKeyLength(params.getDataEncryptionCredential().getSecretKey()), Integer.valueOf(256));
+        Assert.assertEquals(params.getDataEncryptionAlgorithm(), EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256_GCM);
+        Assert.assertNotNull(params.getDataKeyInfoGenerator());
+        Assert.assertTrue(KeyAgreementKeyInfoGenerator.class.isInstance(params.getDataKeyInfoGenerator()));
+        
+        KeyAgreementCredential kaCred = KeyAgreementCredential.class.cast(params.getDataEncryptionCredential());
+        Assert.assertEquals(kaCred.getParameters().size(), 1);
+        Assert.assertTrue(kaCred.getParameters().contains(PBKDF2.class));
+    }
+    
+    @Test
+    public void testGetEffectiveKeyAgreementConfiguration() {
+        SAMLMetadataKeyAgreementEncryptionConfiguration ecConfig1 = new SAMLMetadataKeyAgreementEncryptionConfiguration();
+        ecConfig1.setMetadataUseKeyWrap(KeyWrap.Always);
+        config1.setKeyAgreementConfigurations(Map.of("EC", ecConfig1));
+        
+        SAMLMetadataKeyAgreementEncryptionConfiguration ecConfig2 = new SAMLMetadataKeyAgreementEncryptionConfiguration();
+        ecConfig2.setAlgorithm(EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES);
+        ecConfig2.setParameters(Set.of(new PBKDF2()));
+        ecConfig2.setMetadataUseKeyWrap(KeyWrap.IfNotIndicated);
+        config2.setKeyAgreementConfigurations(Map.of("EC", ecConfig2));
+        
+        SAMLMetadataKeyAgreementEncryptionConfiguration ecConfig3 = new SAMLMetadataKeyAgreementEncryptionConfiguration();
+        ecConfig3.setAlgorithm("SomeAlgo");
+        ecConfig3.setParameters(Set.of(new ConcatKDF()));
+        ecConfig3.setMetadataUseKeyWrap(KeyWrap.Default);
+        config3.setKeyAgreementConfigurations(Map.of("EC", ecConfig3));
+        
+        SAMLMetadataKeyAgreementEncryptionConfiguration config = resolver.getEffectiveKeyAgreementConfiguration(criteriaSet, ecCred1);
+        
+        Assert.assertEquals(config.getAlgorithm(), EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES);
+        Assert.assertEquals(config.getMetadataUseKeyWrap(), KeyWrap.Always);
+        Assert.assertEquals(config.getParameters().size(), 1);
+        Assert.assertTrue(PBKDF2.class.isInstance(config.getParameters().iterator().next()));
+    }
+    
+    @Test
+    public void testDefaultKeyAgreementUseKeyWrap() {
+        KeyAgreementEncryptionConfiguration ecConfig = new KeyAgreementEncryptionConfiguration();
+        ecConfig.setAlgorithm(EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES);
+        ecConfig.setParameters(Set.of());
+        BasicEncryptionConfiguration encConfig = new BasicEncryptionConfiguration();
+        encConfig.setKeyAgreementConfigurations(Map.of("EC", ecConfig));
+        CriteriaSet criteria = new CriteriaSet(new EncryptionConfigurationCriterion(encConfig));
+        
+        // Check default value
+        Assert.assertEquals(resolver.getDefaultKeyAgreemenUseKeyWrap(), KeyWrap.Default);
+        
+        SAMLMetadataKeyAgreementEncryptionConfiguration config = resolver.getEffectiveKeyAgreementConfiguration(criteria, ecCred1);
+        Assert.assertEquals(config.getMetadataUseKeyWrap(), KeyWrap.Default);
+        
+        resolver.setDefaultKeyAgreementUseKeyWrap(KeyWrap.Always);
+        Assert.assertEquals(resolver.getDefaultKeyAgreemenUseKeyWrap(), KeyWrap.Always);
+        
+        config = resolver.getEffectiveKeyAgreementConfiguration(criteria, ecCred1);
+        Assert.assertEquals(config.getMetadataUseKeyWrap(), KeyWrap.Always);
+    }
+    
     @Test
     public void testMultipleKeyDescriptors() throws ResolverException {
         roleDesc.getKeyDescriptors().add(buildKeyDescriptor(dsaCred1KeyName, UsageType.SIGNING, dsaCred1.getPublicKey()));
diff --git a/opensaml-saml-impl/src/test/resources/logback-test.xml b/opensaml-saml-impl/src/test/resources/logback-test.xml
index ac9ab81d9..5933e433c 100644
--- a/opensaml-saml-impl/src/test/resources/logback-test.xml
+++ b/opensaml-saml-impl/src/test/resources/logback-test.xml
@@ -16,6 +16,14 @@
         <level value="WARN"/>
     </logger>
     
+    <logger name="org.opensaml.xmlsec.impl.BasicEncryptionParametersResolver">
+        <level value="TRACE"/>
+    </logger>
+    
+    <logger name="org.opensaml.saml.security.impl.SAMLMetadataEncryptionParametersResolver">
+        <level value="TRACE"/>
+    </logger>
+    
     <logger name="org.opensaml.security.x509.tls.impl.ThreadLocalX509TrustManager">
         <level value="INFO"/>
     </logger>
diff --git a/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/EncryptionConfiguration.java b/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/EncryptionConfiguration.java
index 1d0f295c4..5a70942c7 100644
--- a/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/EncryptionConfiguration.java
+++ b/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/EncryptionConfiguration.java
@@ -18,6 +18,7 @@
 package org.opensaml.xmlsec;
 
 import java.util.List;
+import java.util.Map;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -27,6 +28,7 @@ import net.shibboleth.utilities.java.support.annotation.constraint.NotLive;
 import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
 
 import org.opensaml.security.credential.Credential;
+import org.opensaml.xmlsec.encryption.support.KeyAgreementEncryptionConfiguration;
 import org.opensaml.xmlsec.encryption.support.RSAOAEPParameters;
 import org.opensaml.xmlsec.keyinfo.NamedKeyInfoGeneratorManager;
 
@@ -101,4 +103,12 @@ public interface EncryptionConfiguration extends WhitelistBlacklistConfiguration
      */
     @Nullable public KeyTransportAlgorithmPredicate getKeyTransportAlgorithmPredicate();
     
+    /**
+     * Get the map of {@link KeyAgreementEncryptionConfiguration} instances.
+     * 
+     * @return the 
+     */
+    @Nonnull @Unmodifiable @NotLive
+    public Map<String, KeyAgreementEncryptionConfiguration> getKeyAgreementConfigurations();
+    
 }
\ No newline at end of file
diff --git a/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/agreement/KeyAgreementSupport.java b/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/agreement/KeyAgreementSupport.java
index 241ac63e6..a8db46532 100644
--- a/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/agreement/KeyAgreementSupport.java
+++ b/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/agreement/KeyAgreementSupport.java
@@ -17,10 +17,14 @@
 
 package org.opensaml.xmlsec.agreement;
 
+import java.util.Set;
+
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.opensaml.core.config.ConfigurationService;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.crypto.JCAConstants;
 import org.opensaml.xmlsec.encryption.AgreementMethod;
 import org.opensaml.xmlsec.encryption.EncryptedType;
 import org.opensaml.xmlsec.encryption.EncryptionMethod;
@@ -31,6 +35,9 @@ import org.opensaml.xmlsec.encryption.KeySize;
  */
 public final class KeyAgreementSupport {
     
+    /** JCA key algorithms that support key agreement. */
+    public static final Set<String> KEY_ALGORITHMS = Set.of(JCAConstants.KEY_ALGO_EC);
+    
     /** Constructor. */
     private KeyAgreementSupport() {}
 
@@ -44,6 +51,34 @@ public final class KeyAgreementSupport {
         return ConfigurationService.get(KeyAgreementProcessorRegistry.class);
     }
     
+    /**
+     * Lookup and return the {@link KeyAgreementProcessor} to use for the specified key
+     * agreement algorithm.
+     * 
+     * @param algorithm the key agreement algorithm
+     * 
+     * @return the processor for that algorithm
+     * 
+     * @throws KeyAgreementException if global {@link KeyAgreementProcessorRegistry} is not configured
+     *          or if no processor is registered for the specified algorithm
+     */
+    @Nonnull public static KeyAgreementProcessor getProcessor(@Nonnull final String algorithm)
+            throws KeyAgreementException {
+        
+        final KeyAgreementProcessorRegistry registry = getGlobalProcessorRegistry();
+        if (registry == null) {
+            throw new KeyAgreementException("Global KeyAgreementProcessorRegistry not configured");
+        }
+        
+        final KeyAgreementProcessor processor = registry.getProcessor(algorithm);
+        if (processor == null) {
+            throw new KeyAgreementException("No KeyAgreementProcessor registered for specified algorithm: "
+                    + algorithm);
+        }
+        
+        return processor;
+    }
+    
     /**
      * Look for an explicit key size via an {@link AgreementMethod}'s grandparent's {@link EncryptionMethod}
      * child's {@link KeySize} child element.
@@ -65,4 +100,18 @@ public final class KeyAgreementSupport {
         
         return et.getEncryptionMethod().getKeySize().getValue();
     }
+    
+    /**
+     * Evaluate whether the specified credential contains a public key which supports key agreement.
+     * 
+     * @param credential the credential to evaluate
+     * @return true if supports key agreement, false if does not
+     */
+    public static boolean supportsKeyAgreement(@Nullable final Credential credential) {
+        if (credential == null) {
+            return false;
+        }
+        
+        return credential.getPublicKey() != null && KEY_ALGORITHMS.contains(credential.getPublicKey().getAlgorithm());
+    }
 }
diff --git a/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/algorithm/AlgorithmSupport.java b/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/algorithm/AlgorithmSupport.java
index 51cb6d508..6e1dad94d 100644
--- a/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/algorithm/AlgorithmSupport.java
+++ b/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/algorithm/AlgorithmSupport.java
@@ -249,6 +249,40 @@ public final class AlgorithmSupport {
                 || EncryptionConstants.ALGO_ID_KEYTRANSPORT_RSAOAEP11.equals(keyTransportAlgorithm);
     }
     
+    /**
+     * Check whether the algorithm URI indicates block encryption.
+     * 
+     * @param algorithm the algorithm URI
+     * @return true if URI indicates symmetric key wrap, false otherwise
+     */
+    public static boolean isBlockEncryption(@Nonnull final String algorithm) {
+        final AlgorithmRegistry registry = getGlobalAlgorithmRegistry();
+        if (registry != null){
+            final AlgorithmDescriptor descriptor = registry.get(algorithm);
+            if (descriptor != null) {
+                return descriptor.getType().equals(AlgorithmDescriptor.AlgorithmType.BlockEncryption);
+            }
+        }
+        return false;
+    }
+    
+    /**
+     * Check whether the algorithm URI indicates symmetric key wrap.
+     * 
+     * @param algorithm the algorithm URI
+     * @return true if URI indicates symmetric key wrap, false otherwise
+     */
+    public static boolean isSymmetricKeyWrap(@Nonnull final String algorithm) {
+        final AlgorithmRegistry registry = getGlobalAlgorithmRegistry();
+        if (registry != null){
+            final AlgorithmDescriptor descriptor = registry.get(algorithm);
+            if (descriptor != null) {
+                return descriptor.getType().equals(AlgorithmDescriptor.AlgorithmType.SymmetricKeyWrap);
+            }
+        }
+        return false;
+    }
+    
     /**
      * Check whether the signature method algorithm URI indicates HMAC.
      * 
diff --git a/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/encryption/support/KeyAgreementEncryptionConfiguration.java b/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/encryption/support/KeyAgreementEncryptionConfiguration.java
new file mode 100644
index 000000000..e8cd2da83
--- /dev/null
+++ b/opensaml-xmlsec-api/src/main/java/org/opensaml/xmlsec/encryption/support/KeyAgreementEncryptionConfiguration.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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.opensaml.xmlsec.encryption.support;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.xmlsec.agreement.KeyAgreementParameter;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotLive;
+import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/**
+ * A component representing the specific configuration for a key agreement encryption operation.
+ */
+public class KeyAgreementEncryptionConfiguration {
+    
+    /** The key agreement algorithm URI. */
+    private String algorithm;
+    
+    /** The collection of {@link KeyAgreementParameter}. */
+    @Nullable private Collection<KeyAgreementParameter> parameters;
+
+    /**
+     * Get the algorithm URI.
+     * 
+     * @return the algorithm URI
+     */
+    @Nullable public String getAlgorithm() {
+        return algorithm;
+    }
+
+    /**
+     * Set the algorithm URI.
+     * 
+     * @param uri the algorithm URI
+     */
+    public void setAlgorithm(@Nullable final String uri) {
+        algorithm = StringSupport.trimOrNull(uri);
+    }
+
+    /**
+     * Get the collection of {@link KeyAgreementParameter}.
+     * 
+     * @return the collection of parameters
+     */
+    @Nullable @NonnullElements @NotLive @Unmodifiable
+    public Collection<KeyAgreementParameter> getParameters() {
+        return parameters;
+    }
+
+    /**
+     * Set the collection of {@link KeyAgreementParameter}.
+     * 
+     * @param params the collection of parameters
+     */
+    public void setParameters(@Nullable final Collection<KeyAgreementParameter> params) {
+        if (params == null) {
+            parameters = null;
+        } else {
+            parameters = params.stream().filter(Objects::nonNull).collect(Collectors.toUnmodifiableSet());
+        }
+    }
+
+}
diff --git a/opensaml-xmlsec-api/src/test/java/org/opensaml/xmlsec/algorithm/AlgorithmSupportTest.java b/opensaml-xmlsec-api/src/test/java/org/opensaml/xmlsec/algorithm/AlgorithmSupportTest.java
index 7b231b1c9..a7a048a28 100644
--- a/opensaml-xmlsec-api/src/test/java/org/opensaml/xmlsec/algorithm/AlgorithmSupportTest.java
+++ b/opensaml-xmlsec-api/src/test/java/org/opensaml/xmlsec/algorithm/AlgorithmSupportTest.java
@@ -107,6 +107,37 @@ public class AlgorithmSupportTest extends OpenSAMLInitBaseTestCase {
         Assert.assertFalse(AlgorithmSupport.isDataEncryptionAlgorithm(new DigestSHA256()));
     }
     
+    @Test
+    public void testIsSymmetricKeyWrap() {
+        Assert.assertTrue(AlgorithmSupport.isSymmetricKeyWrap(EncryptionConstants.ALGO_ID_KEYWRAP_AES128));
+        Assert.assertTrue(AlgorithmSupport.isSymmetricKeyWrap(EncryptionConstants.ALGO_ID_KEYWRAP_AES192));
+        Assert.assertTrue(AlgorithmSupport.isSymmetricKeyWrap(EncryptionConstants.ALGO_ID_KEYWRAP_AES256));
+        Assert.assertTrue(AlgorithmSupport.isSymmetricKeyWrap(EncryptionConstants.ALGO_ID_KEYWRAP_TRIPLEDES));
+        
+        //Test some failure cases
+        Assert.assertFalse(AlgorithmSupport.isSymmetricKeyWrap(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128));
+        Assert.assertFalse(AlgorithmSupport.isSymmetricKeyWrap(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128_GCM));
+        Assert.assertFalse(AlgorithmSupport.isSymmetricKeyWrap(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256));
+        Assert.assertFalse(AlgorithmSupport.isSymmetricKeyWrap(SignatureConstants.ALGO_ID_DIGEST_SHA256));
+    }
+
+    @Test
+    public void testIsBlockEncryption() {
+        Assert.assertTrue(AlgorithmSupport.isBlockEncryption(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128));
+        Assert.assertTrue(AlgorithmSupport.isBlockEncryption(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128_GCM));
+        Assert.assertTrue(AlgorithmSupport.isBlockEncryption(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES192));
+        Assert.assertTrue(AlgorithmSupport.isBlockEncryption(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES192_GCM));
+        Assert.assertTrue(AlgorithmSupport.isBlockEncryption(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256));
+        Assert.assertTrue(AlgorithmSupport.isBlockEncryption(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256_GCM));
+        Assert.assertTrue(AlgorithmSupport.isBlockEncryption(EncryptionConstants.ALGO_ID_BLOCKCIPHER_TRIPLEDES));
+        
+        //Test some failure cases
+        Assert.assertFalse(AlgorithmSupport.isBlockEncryption(EncryptionConstants.ALGO_ID_KEYTRANSPORT_RSA15));
+        Assert.assertFalse(AlgorithmSupport.isBlockEncryption(EncryptionConstants.ALGO_ID_KEYWRAP_AES128));
+        Assert.assertFalse(AlgorithmSupport.isBlockEncryption(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256));
+        Assert.assertFalse(AlgorithmSupport.isBlockEncryption(SignatureConstants.ALGO_ID_DIGEST_SHA224));
+    }
+    
     @Test
     public void testCredentialSupportsAlgorithmForSigning() throws NoSuchAlgorithmException, KeyException, NoSuchProviderException {
         Credential credential;
diff --git a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/config/impl/DefaultSecurityConfigurationBootstrap.java b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/config/impl/DefaultSecurityConfigurationBootstrap.java
index 9b81de8a7..1dbbd939c 100644
--- a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/config/impl/DefaultSecurityConfigurationBootstrap.java
+++ b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/config/impl/DefaultSecurityConfigurationBootstrap.java
@@ -19,14 +19,20 @@ package org.opensaml.xmlsec.config.impl;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.security.crypto.JCAConstants;
+import org.opensaml.xmlsec.derivation.impl.ConcatKDF;
 import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver;
 import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver;
 import org.opensaml.xmlsec.encryption.support.EncryptionConstants;
 import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver;
+import org.opensaml.xmlsec.encryption.support.KeyAgreementEncryptionConfiguration;
 import org.opensaml.xmlsec.encryption.support.RSAOAEPParameters;
 import org.opensaml.xmlsec.encryption.support.SimpleKeyInfoReferenceEncryptedKeyResolver;
 import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver;
@@ -48,6 +54,10 @@ import org.opensaml.xmlsec.keyinfo.impl.provider.ECKeyValueProvider;
 import org.opensaml.xmlsec.keyinfo.impl.provider.InlineX509DataProvider;
 import org.opensaml.xmlsec.keyinfo.impl.provider.RSAKeyValueProvider;
 import org.opensaml.xmlsec.signature.support.SignatureConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 
 /**
  * A utility class which programmatically builds basic instances of various components 
@@ -56,6 +66,9 @@ import org.opensaml.xmlsec.signature.support.SignatureConstants;
  */
 public class DefaultSecurityConfigurationBootstrap {
     
+    /** Logger. */
+    private static final Logger LOG = LoggerFactory.getLogger(DefaultSecurityConfigurationBootstrap.class);
+    
     /** Constructor. */
     protected DefaultSecurityConfigurationBootstrap() {}
     
@@ -83,8 +96,8 @@ public class DefaultSecurityConfigurationBootstrap {
                 // The order of the RSA algos is significant.
                 EncryptionConstants.ALGO_ID_KEYTRANSPORT_RSAOAEP,
                 
-                // The order of these is not significant.
-                // These aren't really "preferences" per se. They just need to be registered 
+                // The order of these is only significant when doing key agreement with key wrap.
+                // Otherwise the order is not significant, they just need to be registered 
                 // so that they can be used if a credential with a key of that type and size is seen.
                 EncryptionConstants.ALGO_ID_KEYWRAP_AES128,
                 EncryptionConstants.ALGO_ID_KEYWRAP_AES192,
@@ -98,6 +111,26 @@ public class DefaultSecurityConfigurationBootstrap {
                 null
                 ));
         
+        try {
+            final Map<String, KeyAgreementEncryptionConfiguration> kaConfigs = new HashMap<>();
+            
+            final KeyAgreementEncryptionConfiguration ecConfig = new KeyAgreementEncryptionConfiguration();
+            ecConfig.setAlgorithm(EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES);
+            final ConcatKDF ecConcatKDF = new ConcatKDF();
+            // Need to set these 3 to something to confirm to NIST spec requirements. Actual deployments
+            // can and should override in a custom config with specific parameter values, if needed.
+            ecConcatKDF.setAlgorithmID("00");
+            ecConcatKDF.setPartyUInfo("00");
+            ecConcatKDF.setPartyVInfo("00");
+            ecConcatKDF.initialize();
+            ecConfig.setParameters(Set.of(ecConcatKDF));
+            kaConfigs.put(JCAConstants.KEY_ALGO_EC, ecConfig);
+            
+            config.setKeyAgreementConfigurations(kaConfigs);
+        } catch (final ComponentInitializationException e) {
+            LOG.error("Initialization failure on global key agreement encryption configuration, will be unusable", e);
+        }
+        
         config.setDataKeyInfoGeneratorManager(buildDataEncryptionKeyInfoGeneratorManager());
         config.setKeyTransportKeyInfoGeneratorManager(buildKeyTransportEncryptionKeyInfoGeneratorManager());
         
diff --git a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/impl/BasicEncryptionConfiguration.java b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/impl/BasicEncryptionConfiguration.java
index ac85ba2e1..6c9d061f9 100644
--- a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/impl/BasicEncryptionConfiguration.java
+++ b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/impl/BasicEncryptionConfiguration.java
@@ -19,6 +19,7 @@ package org.opensaml.xmlsec.impl;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -31,6 +32,7 @@ import net.shibboleth.utilities.java.support.primitive.StringSupport;
 import org.opensaml.security.credential.Credential;
 import org.opensaml.xmlsec.EncryptionConfiguration;
 import org.opensaml.xmlsec.KeyTransportAlgorithmPredicate;
+import org.opensaml.xmlsec.encryption.support.KeyAgreementEncryptionConfiguration;
 import org.opensaml.xmlsec.encryption.support.RSAOAEPParameters;
 import org.opensaml.xmlsec.keyinfo.NamedKeyInfoGeneratorManager;
 import org.slf4j.Logger;
@@ -74,6 +76,9 @@ public class BasicEncryptionConfiguration extends BasicWhitelistBlacklistConfigu
     /** Key transport algorithm predicate. */
     @Nullable private KeyTransportAlgorithmPredicate keyTransportPredicate;
     
+    /** Key agreement configurations. */
+    @Nonnull @NonnullElements private Map<String, KeyAgreementEncryptionConfiguration> keyAgreementConfigurations;
+    
     //TODO chaining to parent config instance on getters? or use a wrapping proxy, etc?
     
     //TODO update for modern coding conventions, Guava, etc
@@ -84,6 +89,7 @@ public class BasicEncryptionConfiguration extends BasicWhitelistBlacklistConfigu
         dataEncryptionAlgorithms = Collections.emptyList();
         keyTransportEncryptionCredentials = Collections.emptyList();
         keyTransportEncryptionAlgorithms = Collections.emptyList();
+        keyAgreementConfigurations = Collections.emptyMap();
         
         rsaOAEPParametersMerge = true;
     }
@@ -245,4 +251,22 @@ public class BasicEncryptionConfiguration extends BasicWhitelistBlacklistConfigu
         keyTransportPredicate = predicate;
     }
 
+    /** {@inheritDoc} */
+    @Nonnull public Map<String, KeyAgreementEncryptionConfiguration> getKeyAgreementConfigurations() {
+        return keyAgreementConfigurations;
+    }
+    
+    /**
+     * Set the map of {@link KeyAgreementEncryptionConfiguration} instances.
+     * 
+     * @param configs the new map of instances
+     */
+    public void setKeyAgreementConfigurations(@Nullable final Map<String,KeyAgreementEncryptionConfiguration> configs) {
+        if (configs == null) {
+            keyAgreementConfigurations = Collections.emptyMap();
+        } else {
+            keyAgreementConfigurations = Map.copyOf(configs);
+        }
+    }
+    
 }
\ No newline at end of file
diff --git a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/impl/BasicEncryptionParametersResolver.java b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/impl/BasicEncryptionParametersResolver.java
index 2387b9e79..379af0a9a 100644
--- a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/impl/BasicEncryptionParametersResolver.java
+++ b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/impl/BasicEncryptionParametersResolver.java
@@ -23,6 +23,7 @@ import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Predicate;
 
 import javax.annotation.Nonnull;
@@ -34,11 +35,17 @@ import org.opensaml.xmlsec.EncryptionConfiguration;
 import org.opensaml.xmlsec.EncryptionParameters;
 import org.opensaml.xmlsec.EncryptionParametersResolver;
 import org.opensaml.xmlsec.KeyTransportAlgorithmPredicate;
+import org.opensaml.xmlsec.agreement.KeyAgreementCredential;
+import org.opensaml.xmlsec.agreement.KeyAgreementException;
+import org.opensaml.xmlsec.agreement.KeyAgreementParameters;
+import org.opensaml.xmlsec.agreement.KeyAgreementProcessor;
+import org.opensaml.xmlsec.agreement.KeyAgreementSupport;
 import org.opensaml.xmlsec.algorithm.AlgorithmRegistry;
 import org.opensaml.xmlsec.algorithm.AlgorithmSupport;
 import org.opensaml.xmlsec.criterion.EncryptionConfigurationCriterion;
 import org.opensaml.xmlsec.criterion.EncryptionOptionalCriterion;
 import org.opensaml.xmlsec.criterion.KeyInfoGenerationProfileCriterion;
+import org.opensaml.xmlsec.encryption.support.KeyAgreementEncryptionConfiguration;
 import org.opensaml.xmlsec.encryption.support.RSAOAEPParameters;
 import org.opensaml.xmlsec.keyinfo.KeyInfoGenerator;
 import org.slf4j.Logger;
@@ -196,7 +203,8 @@ public class BasicEncryptionParametersResolver extends AbstractSecurityParameter
             }
             
             log.debug("\tKey transport KeyInfoGenerator: {}", 
-                    params.getKeyTransportKeyInfoGenerator() != null ? "present" : "null");
+                    params.getKeyTransportKeyInfoGenerator() != null ?
+                            params.getKeyTransportKeyInfoGenerator().getClass().getName() : "null");
             
             final Key dataKey = CredentialSupport.extractEncryptionKey(params.getDataEncryptionCredential());
             if (dataKey != null) {
@@ -208,7 +216,8 @@ public class BasicEncryptionParametersResolver extends AbstractSecurityParameter
             log.debug("\tData encryption algorithm URI: {}", params.getDataEncryptionAlgorithm());
             
             log.debug("\tData encryption KeyInfoGenerator: {}", 
-                    params.getDataKeyInfoGenerator() != null ? "present" : "null");
+                    params.getDataKeyInfoGenerator() != null ?
+                            params.getDataKeyInfoGenerator().getClass().getName() : "null");
             
         }
     }
@@ -326,6 +335,11 @@ public class BasicEncryptionParametersResolver extends AbstractSecurityParameter
             params.setDataEncryptionAlgorithm(resolveDataEncryptionAlgorithm(null, dataEncryptionAlgorithms));
         } else {
             for (final Credential dataEncryptionCredential : dataEncryptionCredentials) {
+                if (checkAndProcessKeyAgreement(params, criteria, dataEncryptionCredential, dataEncryptionAlgorithms,
+                        Collections.emptyList())) {
+                    return;
+                }
+                
                 final String dataEncryptionAlgorithm = resolveDataEncryptionAlgorithm(dataEncryptionCredential, 
                         dataEncryptionAlgorithms);
                 if (dataEncryptionAlgorithm != null) {
@@ -343,6 +357,11 @@ public class BasicEncryptionParametersResolver extends AbstractSecurityParameter
         
         // Select key encryption cred and algorithm
         for (final Credential keyTransportCredential : keyTransportCredentials) {
+            if (checkAndProcessKeyAgreement(params, criteria, keyTransportCredential, dataEncryptionAlgorithms,
+                    keyTransportAlgorithms)) {
+                return;
+            }
+            
             final String keyTransportAlgorithm = resolveKeyTransportAlgorithm(keyTransportCredential,
                     keyTransportAlgorithms, params.getDataEncryptionAlgorithm(), keyTransportPredicate);
             
@@ -362,6 +381,145 @@ public class BasicEncryptionParametersResolver extends AbstractSecurityParameter
         processDataEncryptionCredentialAutoGeneration(params);
     }
     
+    /**
+     * Check for a credential type that implies a key agreement operation, and process if so indicated.
+     * 
+     * <p>
+     * For both algorithm list arguments, they are assumed to already have had runtime support and include/exclude
+     * filtering applied.
+     * </p>
+     * 
+     * <p>
+     * If symmetric key wrap should NOT be considered, pass an empty list for <code>keyTransportAlgorithms</code>.
+     * Otherwise, if the <code>keyTransportAlgorithms</code> list contains a symmetric key wrap algorithm, then 
+     * key wrapping will be indicated in the produced parameters.  If it does not then direct data encryption
+     * will be indicated.
+     * </p>
+     * 
+     * @param params the params instance being populated
+     * @param criteria the input criteria being evaluated
+     * @param credential the credential being evaluated
+     * @param dataEncryptionAlgorithms the effective data encryption credentials
+     * @param keyTransportAlgorithms the effective key transport credentials
+     * 
+     * @return true if all required parameters were supplied, key agreement was successfully performed,
+     *         and the {@link EncryptionParameters} instance's credential and algorithms properties are fully populated,
+     *         otherwise false
+     */
+    protected boolean checkAndProcessKeyAgreement(@Nonnull final EncryptionParameters params,
+            @Nonnull final CriteriaSet criteria, @Nonnull final Credential credential,
+            @Nonnull final List<String> dataEncryptionAlgorithms, @Nonnull final List<String> keyTransportAlgorithms) {
+        
+        if (!KeyAgreementSupport.supportsKeyAgreement(credential) ) {
+            log.trace("Specified Credential does not support key agreement");
+            return false; 
+        }
+        
+        log.debug("Processing key agreement for credential with key type: {}",
+                credential.getPublicKey().getAlgorithm());
+        
+        final KeyAgreementEncryptionConfiguration config = getEffectiveKeyAgreementConfiguration(criteria, credential);
+        if (config == null) {
+            log.warn("Unable to get effective KeyAgreementEncryptionConfiguration for credential with key type: {}",
+                    credential.getPublicKey().getAlgorithm());
+            return false;
+        }
+        
+        final String dataEncryptionAlgorithm = dataEncryptionAlgorithms.stream()
+                .filter(AlgorithmSupport::isBlockEncryption)
+                .findFirst().orElse(null);
+        if (dataEncryptionAlgorithm == null) {
+            log.warn("Unable to resolve data encryption algorithm for key agreement, skipping");
+            return false;
+        }
+        
+        final String keyTransportAlgorithm = keyTransportAlgorithms.stream()
+                .filter(AlgorithmSupport::isSymmetricKeyWrap)
+                .findFirst().orElse(null);
+        
+        final String keyAlgorithm = keyTransportAlgorithm != null ? keyTransportAlgorithm : dataEncryptionAlgorithm;
+        
+        final KeyAgreementParameters parameters = new KeyAgreementParameters(config.getParameters(), true);
+        try {
+            parameters.initializeAll();
+            parameters.forEach(p -> {
+                log.debug("Saw KeyAgreementParameter of type: {}", p.getClass().getName());
+            });
+        } catch (final KeyAgreementException e) {
+            log.warn("Fatal error initing configured parameters for key agreement", e);
+            return false;
+        }
+        
+        try {
+            final KeyAgreementProcessor processor = KeyAgreementSupport.getProcessor(config.getAlgorithm());
+        
+            final KeyAgreementCredential agreementCredential = processor.execute(credential, keyAlgorithm, parameters);
+            
+            params.setDataEncryptionAlgorithm(dataEncryptionAlgorithm);
+            
+            if (keyTransportAlgorithm != null) {
+                params.setKeyTransportEncryptionAlgorithm(keyTransportAlgorithm);
+                params.setKeyTransportEncryptionCredential(agreementCredential);
+            } else {
+                params.setDataEncryptionCredential(agreementCredential);
+            }
+            
+            processDataEncryptionCredentialAutoGeneration(params);
+            
+            log.debug("Successfully processed key agreement for credential with key type: {}",
+                    credential.getPublicKey().getAlgorithm());
+            
+            return true;
+        } catch (final KeyAgreementException e) {
+            log.warn("Fatal error processing key agreement for credential", e);
+            return false;
+        }
+        
+    }
+    
+    /**
+     * Get the effective {@link KeyAgreementEncryptionConfiguration} to use with the specified credential.
+     * 
+     * @param criteria the criteria
+     * @param credential the credential to evaluate
+     * @return the key agreement configuration for the credential, or null if could not be resolved
+     */
+    @Nullable protected KeyAgreementEncryptionConfiguration getEffectiveKeyAgreementConfiguration(
+            @Nonnull final CriteriaSet criteria, @Nonnull final Credential credential) {
+        
+        final String keyType = credential.getPublicKey().getAlgorithm();
+        
+        final KeyAgreementEncryptionConfiguration config = new KeyAgreementEncryptionConfiguration();
+        
+        final List<EncryptionConfiguration> encConfigs = criteria.get(EncryptionConfigurationCriterion.class)
+                .getConfigurations();
+        
+        config.setAlgorithm(
+                encConfigs.stream()
+                    .map(c -> c.getKeyAgreementConfigurations().get(keyType))
+                    .filter(Objects::nonNull)
+                    .map(KeyAgreementEncryptionConfiguration::getAlgorithm)
+                    .filter(Objects::nonNull)
+                    .findFirst().orElse(null)
+                );
+        
+        config.setParameters(
+                encConfigs.stream()
+                    .map(c -> c.getKeyAgreementConfigurations().get(keyType))
+                    .filter(Objects::nonNull)
+                    .map(KeyAgreementEncryptionConfiguration::getParameters)
+                    .filter(Objects::nonNull)
+                    .findFirst().orElse(Collections.emptySet())
+                );
+        
+        if (config.getAlgorithm() == null) {
+            log.warn("Failed to resolve a key agreement algorithm for key type: {}", keyType);
+            return null;
+        }
+        
+        return config;
+    }
+    
     /**
      * Resolve and populate an instance of {@link RSAOAEPParameters}, if appropriate for the selected
      * key transport encryption algorithm.
diff --git a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/impl/BasicSignatureSigningParametersResolver.java b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/impl/BasicSignatureSigningParametersResolver.java
index 77eeb4ee8..5cebfe214 100644
--- a/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/impl/BasicSignatureSigningParametersResolver.java
+++ b/opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/impl/BasicSignatureSigningParametersResolver.java
@@ -154,7 +154,8 @@ public class BasicSignatureSigningParametersResolver
             
             log.debug("\tSignature algorithm URI: {}", params.getSignatureAlgorithm()); 
             
-            log.debug("\tSignature KeyInfoGenerator: {}", params.getKeyInfoGenerator() != null ? "present" : "null");
+            log.debug("\tSignature KeyInfoGenerator: {}", params.getKeyInfoGenerator() != null ?
+                    params.getKeyInfoGenerator().getClass().getName() : "null");
             
             log.debug("\tReference digest method algorithm URI: {}", params.getSignatureReferenceDigestMethod()); 
             log.debug("\tReference canonicalization algorithm URI: {}", 
diff --git a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/impl/BasicEncryptionParametersResolverTest.java b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/impl/BasicEncryptionParametersResolverTest.java
index a4ba046e5..52d3ff865 100644
--- a/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/impl/BasicEncryptionParametersResolverTest.java
+++ b/opensaml-xmlsec-impl/src/test/java/org/opensaml/xmlsec/impl/BasicEncryptionParametersResolverTest.java
@@ -19,15 +19,18 @@ package org.opensaml.xmlsec.impl;
 
 import static org.testng.Assert.*;
 
+import java.security.InvalidAlgorithmParameterException;
 import java.security.KeyPair;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
+import java.security.spec.ECGenParameterSpec;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.annotation.Nullable;
 import javax.crypto.SecretKey;
@@ -43,14 +46,21 @@ import org.opensaml.security.crypto.JCAConstants;
 import org.opensaml.security.crypto.KeySupport;
 import org.opensaml.xmlsec.EncryptionParameters;
 import org.opensaml.xmlsec.KeyTransportAlgorithmPredicate;
+import org.opensaml.xmlsec.agreement.KeyAgreementCredential;
 import org.opensaml.xmlsec.criterion.EncryptionConfigurationCriterion;
 import org.opensaml.xmlsec.criterion.KeyInfoGenerationProfileCriterion;
+import org.opensaml.xmlsec.derivation.impl.ConcatKDF;
+import org.opensaml.xmlsec.derivation.impl.PBKDF2;
 import org.opensaml.xmlsec.encryption.support.EncryptionConstants;
+import org.opensaml.xmlsec.encryption.support.KeyAgreementEncryptionConfiguration;
 import org.opensaml.xmlsec.encryption.support.RSAOAEPParameters;
 import org.opensaml.xmlsec.keyinfo.NamedKeyInfoGeneratorManager;
 import org.opensaml.xmlsec.keyinfo.impl.BasicKeyInfoGeneratorFactory;
+import org.opensaml.xmlsec.keyinfo.impl.KeyAgreementKeyInfoGeneratorFactory;
+import org.opensaml.xmlsec.keyinfo.impl.KeyAgreementKeyInfoGeneratorFactory.KeyAgreementKeyInfoGenerator;
 import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory;
 import org.opensaml.xmlsec.signature.support.SignatureConstants;
+import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -68,9 +78,10 @@ public class BasicEncryptionParametersResolverTest extends XMLObjectBaseTestCase
     
     private BasicEncryptionConfiguration config1, config2, config3;
     
-    private Credential rsaCred1, aes128Cred1, aes192Cred1, aes256Cred1;
+    private Credential rsaCred1, ecCred1, aes128Cred1, aes192Cred1, aes256Cred1;
     
     private String rsaCred1KeyName = "RSACred1";
+    private String ecCred1KeyName = "ECCred1";
     private String aes128Cred1KeyName = "AES128Cred1";
     private String aes192Cred1KeyName = "AES192Cred1";
     private String aes256Cred1KeyName = "AES256Cred1";
@@ -84,11 +95,15 @@ public class BasicEncryptionParametersResolverTest extends XMLObjectBaseTestCase
     private NamedKeyInfoGeneratorManager defaultDataEncryptionKeyInfoGeneratorManager = new NamedKeyInfoGeneratorManager();
     
     @BeforeClass
-    public void buildCredentials() throws NoSuchAlgorithmException, NoSuchProviderException {
+    public void buildCredentials() throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
         KeyPair rsaKeyPair = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_RSA, 2048, null);
         rsaCred1 = CredentialSupport.getSimpleCredential(rsaKeyPair.getPublic(), rsaKeyPair.getPrivate());
         rsaCred1.getKeyNames().add(rsaCred1KeyName);
         
+        KeyPair ecKeyPair = KeySupport.generateKeyPair(JCAConstants.KEY_ALGO_EC, new ECGenParameterSpec("secp256r1"), null);
+        ecCred1 = CredentialSupport.getSimpleCredential(ecKeyPair.getPublic(), ecKeyPair.getPrivate());
+        ecCred1.getKeyNames().add(ecCred1KeyName);
+        
         SecretKey aes128Key = KeySupport.generateKey(JCAConstants.KEY_ALGO_AES, 128, null);
         aes128Cred1 = CredentialSupport.getSimpleCredential(aes128Key);
         aes128Cred1.getKeyNames().add(aes128Cred1KeyName);
@@ -130,25 +145,38 @@ public class BasicEncryptionParametersResolverTest extends XMLObjectBaseTestCase
                 EncryptionConstants.ALGO_ID_KEYWRAP_TRIPLEDES
                 ));
         
+        KeyAgreementEncryptionConfiguration ecConfig = new KeyAgreementEncryptionConfiguration();
+        ecConfig.setAlgorithm(EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES);
+        ConcatKDF concatKDF = new ConcatKDF();
+        concatKDF.setAlgorithmID("00");
+        concatKDF.setPartyUInfo("00");
+        concatKDF.setPartyVInfo("00");
+        ecConfig.setParameters(Set.of(concatKDF));
+        config3.setKeyAgreementConfigurations(Map.of("EC", ecConfig));
+        
         BasicKeyInfoGeneratorFactory basicFactory1 = new BasicKeyInfoGeneratorFactory();
         X509KeyInfoGeneratorFactory x509Factory1 = new X509KeyInfoGeneratorFactory();
+        KeyAgreementKeyInfoGeneratorFactory kaFactory1 = new KeyAgreementKeyInfoGeneratorFactory();
         defaultKeyTransportKeyInfoGeneratorManager = new NamedKeyInfoGeneratorManager();
         defaultKeyTransportKeyInfoGeneratorManager.registerDefaultFactory(basicFactory1);
         defaultKeyTransportKeyInfoGeneratorManager.registerDefaultFactory(x509Factory1);
+        defaultKeyTransportKeyInfoGeneratorManager.registerDefaultFactory(kaFactory1);
         config3.setKeyTransportKeyInfoGeneratorManager(defaultKeyTransportKeyInfoGeneratorManager);
         
         BasicKeyInfoGeneratorFactory basicFactory2 = new BasicKeyInfoGeneratorFactory();
         X509KeyInfoGeneratorFactory x509Factory2 = new X509KeyInfoGeneratorFactory();
+        KeyAgreementKeyInfoGeneratorFactory kaFactory2 = new KeyAgreementKeyInfoGeneratorFactory();
         defaultDataEncryptionKeyInfoGeneratorManager = new NamedKeyInfoGeneratorManager();
         defaultDataEncryptionKeyInfoGeneratorManager.registerDefaultFactory(basicFactory2);
         defaultDataEncryptionKeyInfoGeneratorManager.registerDefaultFactory(x509Factory2);
+        defaultDataEncryptionKeyInfoGeneratorManager.registerDefaultFactory(kaFactory2);
         config3.setDataKeyInfoGeneratorManager(defaultDataEncryptionKeyInfoGeneratorManager);
         
         criterion = new EncryptionConfigurationCriterion(config1, config2, config3);
         
         criteriaSet = new CriteriaSet(criterion);
     }
-    
+
     @Test
     public void testBasicRSA() throws ResolverException {
         config1.setKeyTransportEncryptionCredentials(Collections.singletonList(rsaCred1));
@@ -295,6 +323,172 @@ public class BasicEncryptionParametersResolverTest extends XMLObjectBaseTestCase
         assertTrue(params.getRSAOAEPParameters().isEmpty());
     }
     
+    @Test
+    public void testECDHWithDirectDataEncryption() throws ResolverException {
+        config1.setDataEncryptionCredentials(Collections.singletonList(ecCred1));
+        
+        EncryptionParameters params = resolver.resolveSingle(criteriaSet);
+        
+        assertNotNull(params);
+        assertNull(params.getKeyTransportEncryptionCredential());
+        assertNull(params.getKeyTransportEncryptionAlgorithm());
+        assertNull(params.getKeyTransportKeyInfoGenerator());
+        
+        assertNotNull(params.getDataEncryptionCredential());
+        assertTrue(KeyAgreementCredential.class.isInstance(params.getDataEncryptionCredential()));
+        assertNotNull(params.getDataEncryptionCredential().getSecretKey());
+        assertEquals(params.getDataEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        assertEquals(KeySupport.getKeyLength(params.getDataEncryptionCredential().getSecretKey()), Integer.valueOf(128));
+        assertEquals(params.getDataEncryptionAlgorithm(), defaultAES128DataAlgo);
+        assertNotNull(params.getDataKeyInfoGenerator());
+        assertTrue(KeyAgreementKeyInfoGenerator.class.isInstance(params.getDataKeyInfoGenerator()));
+    }
+    
+    @Test
+    public void testECDHWithDirectDataEncryptionAndAlgorithmOverrides() throws ResolverException {
+        config1.setDataEncryptionCredentials(Collections.singletonList(ecCred1));
+        
+        config2.setDataEncryptionAlgorithms(Collections.singletonList(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256));
+        
+        EncryptionParameters params = resolver.resolveSingle(criteriaSet);
+        
+        assertNotNull(params);
+        assertNull(params.getKeyTransportEncryptionCredential());
+        assertNull(params.getKeyTransportEncryptionAlgorithm());
+        assertNull(params.getKeyTransportKeyInfoGenerator());
+        
+        assertNotNull(params.getDataEncryptionCredential());
+        assertTrue(KeyAgreementCredential.class.isInstance(params.getDataEncryptionCredential()));
+        assertNotNull(params.getDataEncryptionCredential().getSecretKey());
+        assertEquals(params.getDataEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        assertEquals(KeySupport.getKeyLength(params.getDataEncryptionCredential().getSecretKey()), Integer.valueOf(256));
+        assertEquals(params.getDataEncryptionAlgorithm(), EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256);
+        assertNotNull(params.getDataKeyInfoGenerator());
+        assertTrue(KeyAgreementKeyInfoGenerator.class.isInstance(params.getDataKeyInfoGenerator()));
+    }
+    
+    @Test
+    public void testECDHWithKeyWrap() throws ResolverException {
+        config1.setKeyTransportEncryptionCredentials(Collections.singletonList(ecCred1));
+        
+        EncryptionParameters params = resolver.resolveSingle(criteriaSet);
+        
+        assertNotNull(params.getKeyTransportEncryptionCredential());
+        assertTrue(KeyAgreementCredential.class.isInstance(params.getKeyTransportEncryptionCredential()));
+        assertNotNull(params.getKeyTransportEncryptionCredential().getSecretKey());
+        assertEquals(params.getKeyTransportEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        assertEquals(KeySupport.getKeyLength(params.getKeyTransportEncryptionCredential().getSecretKey()), Integer.valueOf(128));
+        assertEquals(params.getKeyTransportEncryptionAlgorithm(), EncryptionConstants.ALGO_ID_KEYWRAP_AES128);
+        assertNotNull(params.getKeyTransportKeyInfoGenerator());
+        assertTrue(KeyAgreementKeyInfoGenerator.class.isInstance(params.getKeyTransportKeyInfoGenerator()));
+        
+        assertNull(params.getDataEncryptionCredential());
+        assertEquals(params.getDataEncryptionAlgorithm(), defaultAES128DataAlgo);
+        assertNull(params.getDataKeyInfoGenerator());
+    }
+    
+    @Test
+    public void testECDHWithKeyWrapAndAlgorithmOverrides() throws ResolverException {
+        config1.setKeyTransportEncryptionCredentials(Collections.singletonList(ecCred1));
+        
+        config2.setKeyTransportEncryptionAlgorithms(Collections.singletonList(EncryptionConstants.ALGO_ID_KEYWRAP_AES256));
+        config2.setDataEncryptionAlgorithms(Collections.singletonList(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES192));
+        
+        EncryptionParameters params = resolver.resolveSingle(criteriaSet);
+        
+        assertNotNull(params.getKeyTransportEncryptionCredential());
+        assertTrue(KeyAgreementCredential.class.isInstance(params.getKeyTransportEncryptionCredential()));
+        assertNotNull(params.getKeyTransportEncryptionCredential().getSecretKey());
+        assertEquals(params.getKeyTransportEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        assertEquals(KeySupport.getKeyLength(params.getKeyTransportEncryptionCredential().getSecretKey()), Integer.valueOf(256));
+        assertEquals(params.getKeyTransportEncryptionAlgorithm(), EncryptionConstants.ALGO_ID_KEYWRAP_AES256);
+        assertNotNull(params.getKeyTransportKeyInfoGenerator());
+        assertTrue(KeyAgreementKeyInfoGenerator.class.isInstance(params.getKeyTransportKeyInfoGenerator()));
+        
+        assertNull(params.getDataEncryptionCredential());
+        assertEquals(params.getDataEncryptionAlgorithm(), EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES192);
+        assertNull(params.getDataKeyInfoGenerator());
+    }
+    
+    @Test
+    public void testECDHWithKeyWrapAndGeneratedDataCredential() throws ResolverException {
+        config1.setKeyTransportEncryptionCredentials(Collections.singletonList(ecCred1));
+        
+        resolver.setAutoGenerateDataEncryptionCredential(true);
+        
+        EncryptionParameters params = resolver.resolveSingle(criteriaSet);
+        
+        assertNotNull(params.getKeyTransportEncryptionCredential());
+        assertTrue(KeyAgreementCredential.class.isInstance(params.getKeyTransportEncryptionCredential()));
+        assertNotNull(params.getKeyTransportEncryptionCredential().getSecretKey());
+        assertEquals(params.getKeyTransportEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        assertEquals(KeySupport.getKeyLength(params.getKeyTransportEncryptionCredential().getSecretKey()), Integer.valueOf(128));
+        assertEquals(params.getKeyTransportEncryptionAlgorithm(), EncryptionConstants.ALGO_ID_KEYWRAP_AES128);
+        assertNotNull(params.getKeyTransportKeyInfoGenerator());
+        assertTrue(KeyAgreementKeyInfoGenerator.class.isInstance(params.getKeyTransportKeyInfoGenerator()));
+        
+        assertNotNull(params.getDataEncryptionCredential());
+        assertNotNull(params.getDataEncryptionCredential().getSecretKey());
+        assertEquals(params.getDataEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        assertEquals(KeySupport.getKeyLength(params.getDataEncryptionCredential().getSecretKey()), Integer.valueOf(128));
+        assertEquals(params.getDataEncryptionAlgorithm(), defaultAES128DataAlgo);
+        assertNotNull(params.getDataKeyInfoGenerator());
+    }
+    
+    @Test
+    public void testECDHWithKDFOverride() throws ResolverException {
+        config1.setDataEncryptionCredentials(Collections.singletonList(ecCred1));
+        
+        KeyAgreementEncryptionConfiguration ecConfig = new KeyAgreementEncryptionConfiguration();
+        PBKDF2 kdf = new PBKDF2();
+        ecConfig.setParameters(Set.of(kdf));
+        config2.setKeyAgreementConfigurations(Map.of("EC", ecConfig));
+        
+        EncryptionParameters params = resolver.resolveSingle(criteriaSet);
+        
+        assertNotNull(params);
+        assertNull(params.getKeyTransportEncryptionCredential());
+        assertNull(params.getKeyTransportEncryptionAlgorithm());
+        assertNull(params.getKeyTransportKeyInfoGenerator());
+        
+        assertNotNull(params.getDataEncryptionCredential());
+        assertTrue(KeyAgreementCredential.class.isInstance(params.getDataEncryptionCredential()));
+        assertNotNull(params.getDataEncryptionCredential().getSecretKey());
+        assertEquals(params.getDataEncryptionCredential().getSecretKey().getAlgorithm(), "AES");
+        assertEquals(KeySupport.getKeyLength(params.getDataEncryptionCredential().getSecretKey()), Integer.valueOf(128));
+        assertEquals(params.getDataEncryptionAlgorithm(), defaultAES128DataAlgo);
+        assertNotNull(params.getDataKeyInfoGenerator());
+        assertTrue(KeyAgreementKeyInfoGenerator.class.isInstance(params.getDataKeyInfoGenerator()));
+        
+        KeyAgreementCredential kaCred = KeyAgreementCredential.class.cast(params.getDataEncryptionCredential());
+        assertEquals(kaCred.getParameters().size(), 1);
+        assertTrue(kaCred.getParameters().contains(PBKDF2.class));
+    }
+    
+    @Test
+    public void testGetEffectiveKeyAgreementConfiguration() {
+        KeyAgreementEncryptionConfiguration ecConfig1 = new KeyAgreementEncryptionConfiguration();
+        ecConfig1.setParameters(Set.of(new ConcatKDF()));
+        config1.setKeyAgreementConfigurations(Map.of("EC", ecConfig1));
+        
+        KeyAgreementEncryptionConfiguration ecConfig2 = new KeyAgreementEncryptionConfiguration();
+        ecConfig2.setAlgorithm(EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES);
+        ecConfig2.setParameters(Set.of(new PBKDF2()));
+        config2.setKeyAgreementConfigurations(Map.of("EC", ecConfig2));
+        
+        KeyAgreementEncryptionConfiguration ecConfig3 = new KeyAgreementEncryptionConfiguration();
+        ecConfig3.setAlgorithm("SomeAlgo");
+        ecConfig3.setParameters(Set.of(new ConcatKDF()));
+        config3.setKeyAgreementConfigurations(Map.of("EC", ecConfig3));
+        
+        KeyAgreementEncryptionConfiguration config = resolver.getEffectiveKeyAgreementConfiguration(criteriaSet, ecCred1);
+        
+        Assert.assertEquals(config.getAlgorithm(), EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES);
+        Assert.assertEquals(config.getParameters().size(), 1);
+        Assert.assertTrue(ConcatKDF.class.isInstance(config.getParameters().iterator().next()));
+        Assert.assertSame(config.getParameters().iterator().next(), ecConfig1.getParameters().iterator().next());
+    }
+    
     @Test
     public void testAES128KeyWrap() throws ResolverException {
         config1.setKeyTransportEncryptionCredentials(Collections.singletonList(aes128Cred1));

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list