Crypto and security extensions to OpenSAML
NOTE: OpenSAML has introduced support for ECDH key agreement starting from version 4.1.0. Therefore, this functionality has been removed from the opensaml-security-ext library in version 3.0.0. If you are using OpenSAML 4.0.X you need to use version 2.1.0 of opensaml-security-ext and 1.0.8 if you are still using OpenSAML 3.
NOTE: There is an interoperability issue between OpenSAML 4.0.X and version 1.0.7 regarding the use of ECDH. The issue concerns the requirements on presence of ConcatKDF parameters in ECDH where OpenSAML does not allow the empty parameters sent by 1.0.7. An issue is raised with the OpenSAML team to resolve this issue to bring OpenSAML 4 in alignment with 1.0.7 if possible. Implementations still using OpenSAML 3 should move to the fixed version 1.0.8 to avoid this issue.
The opensaml-security-ext extends the core OpenSAML libraries with the capability to encrypt and decrypt XML data using ephemeral-static ECDH key agreement (pre-3.0 only).
The library also offers a workaround for using RSA-OAEP and RSA-PSS with HSM protected keys since the Sun PKCS#11 provider does not support RSA-OAEP and RSA-PSS padding.
As of version 1.0.2, the signature algorithms http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1
,
http://www.w3.org/2007/05/xmldsig-more#sha384-rsa-MGF1
and http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1
are represented as OpenSAML algorithm descriptors and installed in the OpenSAML algorithm registry.
Java API documentation of the opensaml-security-ext library is found at:
Generated project information is found at https://docs.swedenconnect.se/opensaml-security-ext/site.
The opensaml-security-ext project artifacts are published to Maven central.
Include the following snippet in your Maven POM to add opensaml-security-ext as a dependency for your project.
<dependency>
<groupId>se.swedenconnect.opensaml</groupId>
<artifactId>opensaml-security-ext</artifactId>
<version>${opensaml-security-ext.version}</version>
</dependency>
OpenSAML needs to be initialized in order to function. The opensaml-security-ext provides the singleton class OpenSAMLInitializer for this purpose.
One or more OpenSAMLInitializerConfig instances may be supplied as arguments to the OpenSAMLInitializer.initialize
method in order to add customized configuration.
In order to utilize the extensions from this library, the OpenSAMLSecurityExtensionConfig should be supplied in the initialize
-call.
It is also possible to configure other algorithm defaults than what is the OpenSAML defaults. This is done by using the OpenSAMLSecurityDefaultsConfig class that takes a SecurityConfiguration instance. In the example below the security configuration is set up according to SAML2Int.
// Initialize OpenSAML and the security extensions.
// We also configure algorithm defaults according to SAML2Int ...
//
OpenSAMLInitializer.getInstance().initialize(
new OpenSAMLSecurityDefaultsConfig(new SAML2IntSecurityConfiguration()),
new OpenSAMLSecurityExtensionConfig());
For our test cases we had to add the Bouncy Castle crypto provider manually in order to implement ECDH. It should be sufficient to have it in the class path, but to be safe, the
preInitialize
method of theOpenSAMLSecurityExtensionConfig
checks whether this provider is installed and does so if it isn't already installed.
Note: The eidas-opensaml library uses opensaml-security-ext. It defines SecurityConfiguration
classes for eIDAS security configuration, one "strict" will small chances of interoperability and one "relaxed" that will actually work against a node using the CEF-software.
Applies to versions earlier than 2.X of the opensaml-security-ext only. More recent versions of the library have removed the below described functionality since it has been added to OpenSAML (4.1.0).
In order to add support for key agreement to OpenSAML in a way that an application that wishes to have this support only needs to make configuration changes we had to add quite a number of different extensions. We tested this on a Shibboleth deployment and managed to add ECDH support to an IdP.
When encrypting a SAML object, for example an Assertion
, for a peer, the following steps are generally taken:
- Locate the peer metadata/credentials.
- Resolve the encryption parameters to use during the encryption process using a
EncryptionParametersResolver
. - Encrypt the data.
Below we illustrate how this is done using the ExtendedSAMLMetadataEncryptionParametersResolver. For details, see resolvedEncryptionParametersFromMetadata
method in the EncryptionDecryptionTest.java file.
// The peer metadata.
final EntityDescriptor metadata = ...;
// Set up a MetadataCredentialResolver (a resolver that reads from SAML metadata)
MetadataCredentialResolver credentialResolver = new MetadataCredentialResolver();
credentialResolver.setKeyInfoCredentialResolver(
DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver());
credentialResolver.initialize();
// Set up the criteria ...
//
// We need default algorithms (in case no are given in EncryptionMethod in metadata).
EncryptionConfigurationCriterion encConfCriterion = new EncryptionConfigurationCriterion(
ExtendedDefaultSecurityConfigurationBootstrap.buildDefaultEncryptionConfiguration());
// RoleDescriptorCriterion gives us the metadata. In a real case a RoleDescriptorResolver
// would be used.
RoleDescriptorCriterion rdCriterion =
new RoleDescriptorCriterion(metadata.getRoleDescriptors().get(0));
CriteriaSet criteriaSet = new CriteriaSet(encConfCriterion, rdCriterion);
// Resolve encryption parameters and encrypt.
//
ExtendedSAMLMetadataEncryptionParametersResolver resolver =
new ExtendedSAMLMetadataEncryptionParametersResolver(credentialResolver);
EncryptionParameters params = resolver.resolveSingle(criteriaSet);
Encrypter encrypter = new Encrypter();
EncryptedData encryptedData = encrypter.encryptElement(this.encryptedObject,
new DataEncryptionParameters(params), new KeyEncryptionParameters(params, metadata.getEntityID()));
The encrypted data is represented in XML as:
<xenc:EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<xenc:EncryptedKey Recipient="http://id.example.com/sp1"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#kw-aes256"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"/>
<ds:KeyInfo>
<xenc:AgreementMethod Algorithm="http://www.w3.org/2009/xmlenc11#ECDH-ES">
<xenc11:KeyDerivationMethod Algorithm="http://www.w3.org/2009/xmlenc11#ConcatKDF"
xmlns:xenc11="http://www.w3.org/2009/xmlenc11#">
<xenc11:ConcatKDFParams AlgorithmID="0000" PartyUInfo="0000" PartyVInfo="0000">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"/>
</xenc11:ConcatKDFParams>
</xenc11:KeyDerivationMethod>
<xenc:OriginatorKeyInfo>
<ds:KeyValue>
<ds11:ECKeyValue xmlns:ds11="http://www.w3.org/2009/xmldsig11#">
<ds11:NamedCurve URI="urn:oid:1.2.840.10045.3.1.7"/>
<ds11:PublicKey>BPqJLXfFWIjsa9hPug...umuc=</ds11:PublicKey>
</ds11:ECKeyValue>
</ds:KeyValue>
</xenc:OriginatorKeyInfo>
<xenc:RecipientKeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Certificate>MIIB...AEoizR</ds:X509Certificate>
</ds:X509Data>
</xenc:RecipientKeyInfo>
</xenc:AgreementMethod>
</ds:KeyInfo>
<xenc:CipherData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:CipherValue>3i2e4G/2LK2oSo...dE1cluerju0sQ==</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedKey>
</ds:KeyInfo>
<xenc:CipherData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:CipherValue>WreMTql...rXWg4=</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
The decryption phase looks like:
Credential[] localCredentials = ...;
Decrypter decrypter = new Decrypter(DecryptionUtils.createDecryptionParameters(localCredentials));
decrypter.setRootInNewDocument(true);
Type decryptedObject = (Type) decrypter.decryptData(encryptedData);
The trick here that allows us to decrypt the above data is the KeyAgreementMethodKeyInfoProvider. This is a special purpose provider that handles key agreement and sets up a KeyAgreementCredential
that makes it possible to use the standard OpenSAML decrypter. See DecryptionUtils for how to set up decryption parameters.
The opensaml-security-ext library also offers another encryption parameter provider, the ExtendedEncryptionParametersResolver. This provider does not locate peer credentials in metadata. Instead we hand them over directly. For details, see resolvedEncryptionParameters
method in the EncryptionDecryptionTest.java file.
// We use the default encryption configuration. The extended part introduces support
// for key agreement and key derivation configuration.
//
BasicExtendedEncryptionConfiguration config =
ExtendedDefaultSecurityConfigurationBootstrap.buildDefaultEncryptionConfiguration();
// Install our key transport encryption credentials.
// The setKeyTransportEncryptionCredentials will analyze whether the added credential can be
// used for ordinary key transport or key agreement.
// Note: You may also use the setKeyAgreementCredentials to explicitly assign credentials that
// may be used for key agreement.
//
config.setKeyTransportEncryptionCredentials(Arrays.asList(peerCredential1, peerCredential2));
// Make our encryption configuration into a criteria for the resolver.
//
EncryptionConfigurationCriterion criterion = new EncryptionConfigurationCriterion(config);
CriteriaSet criteriaSet = new CriteriaSet(criterion);
// Instantiate our extension of the EncryptionParametersResolver to get the parameters needed
// for encryption.
//
ExtendedEncryptionParametersResolver resolver = new ExtendedEncryptionParametersResolver();
EncryptionParameters params = resolver.resolveSingle(criteriaSet);
// Encrypt
Encrypter encrypter = new Encrypter();
EncryptedData encryptedData = encrypter.encryptElement(this.encryptedObject,
new DataEncryptionParameters(params), new KeyEncryptionParameters(params, "recipient"));
The decryption phase is the same as the previous example.
Finally, the opensaml-security-ext library offers a way to manually setup the parameters needed for ECDH encryption.
For details, see manualEncryptionSetup
method in the EncryptionDecryptionTest.java file.
In this case we need to use the "hack", ECDHKeyAgreementParameters which is an extension of OpenSAML's KeyEncryptionParameters
class. The ECDHKeyAgreementParameters
has defaults for ECDH using ConcatKDF for key derivation.
// Set up parameters for encryption manually ...
DataEncryptionParameters dataEncryptionParameters = new DataEncryptionParameters();
dataEncryptionParameters.setAlgorithm(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256_GCM);
// In order for ECDH to be possible with OpenSAML's Encrypter class we need to instantiate
// our special purpose key encryption parameters object.
ECDHKeyAgreementParameters kekParams = new ECDHKeyAgreementParameters();
kekParams.setPeerCredential(this.ecPeerCredential);
// The kekParams will use default algorithms for key wrapping and key agreement.
// We also need the special purpose key info generator (for key agreement).
kekParams.setKeyInfoGenerator(
ExtendedDefaultSecurityConfigurationBootstrap
.buildDefaultKeyAgreementKeyInfoGeneratorFactory().newInstance());
// Encrypt
Encrypter encrypter = new Encrypter();
EncryptedData encryptedData = encrypter.encryptElement(this.encryptedObject,
dataEncryptionParameters, kekParams);
The decryption phase is the same as the previous example.
The standard Sun Java PKCS#11 provider does not support RSA-OAEP decryption which is a problem if the decryption key is stored in a HSM accessed through a PKCS#11 API. See this Stack Overflow article.
The Pkcs11Decrypter extends OpenSAML's Decrypter
implementation with a work-around for this problem.
This work-around comprises of:
- Performing a raw RSA decryption on the encrypted data.
- Performing OAEP padding processing on the decrypted data outside of the HSM to extract the decrypted plaintext.
Furthermore, the Sun PKCS#11 provider does not implement PSS-padding, making it impossible to sign using RSA-PSS if the signing key is stored on a HSM and the Sun PKCS#11 provider is used. The opensaml-security-ext library solves this by overriding OpenSAML's standard SignerProvider
(ApacheSantuarioSignerProviderImpl
) with an extension, ExtendedSignerProvider. This extension handles padding in software and only the raw RSA transform is performed on the HSM.
By adding the opensaml-security-ext library to your classpath, the ExtendedSignerProvider
will be made the default OpenSAML signer provider, and when RSA-PSS signing is ordered and the current crypto provider is the Sun PKCS#11 provider, the above described workaround will kick in. Otherwise, the default provider handles the operation.
If you, for some reason, want to disable the ExtendedSignerProvider
functionality, set the system property se.swedenconnect.opensaml.xmlsec.signature.support.provider.ExtendedSignerProvider.disabled
to true
. You can also force the provider to execute all RSA-based signatures by setting the property se.swedenconnect.opensaml.xmlsec.signature.support.provider.ExtendedSignerProvider.testmode
to true
. This is for testing purposes.
Copyright © 2016-2023, Sweden Connect. Licensed under version 2.0 of the Apache License.