diff options
Diffstat (limited to 'eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils')
2 files changed, 262 insertions, 82 deletions
diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/AbstractCredentialProvider.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/AbstractCredentialProvider.java index c0b015be..336741a0 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/AbstractCredentialProvider.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/AbstractCredentialProvider.java @@ -29,18 +29,21 @@ import java.security.interfaces.RSAPrivateKey; import javax.annotation.PostConstruct; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.impl.utils.KeyStoreUtils; +import at.gv.egiz.eaaf.modules.pvp2.PvpConstants; +import at.gv.egiz.eaaf.modules.pvp2.api.credential.EaafX509Credential; import at.gv.egiz.eaaf.modules.pvp2.exception.CredentialsNotAvailableException; +import at.gv.egiz.eaaf.modules.pvp2.exception.SamlSigningException; import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.EaafKeyStoreX509CredentialAdapter; import org.apache.commons.lang3.StringUtils; -import org.opensaml.xml.security.credential.Credential; -import org.opensaml.xml.security.credential.UsageType; -import org.opensaml.xml.security.x509.X509Credential; -import org.opensaml.xml.signature.Signature; -import org.opensaml.xml.signature.SignatureConstants; +import org.opensaml.security.credential.Credential; +import org.opensaml.security.credential.UsageType; +import org.opensaml.xmlsec.signature.Signature; +import org.opensaml.xmlsec.signature.support.SignatureConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -50,7 +53,10 @@ import org.springframework.core.io.ResourceLoader; public abstract class AbstractCredentialProvider { - @Autowired protected ResourceLoader resourceLoader; + @Autowired + protected ResourceLoader resourceLoader; + @Autowired + protected IConfiguration basicConfig; private static final Logger log = LoggerFactory.getLogger(AbstractCredentialProvider.class); @@ -127,24 +133,26 @@ public abstract class AbstractCredentialProvider { * @return Credentials * @throws CredentialsNotAvailableException In case of a credential error */ - public X509Credential getIdpMetaDataSigningCredential() throws CredentialsNotAvailableException { + public EaafX509Credential getIdpMetaDataSigningCredential() throws CredentialsNotAvailableException { try { final EaafKeyStoreX509CredentialAdapter credentials = new EaafKeyStoreX509CredentialAdapter(keyStore, - getMetadataKeyAlias(), getMetadataKeyPassword().toCharArray()); + getMetadataKeyAlias(), getMetadataKeyPassword().toCharArray(), getFriendlyName()); credentials.setUsageType(UsageType.SIGNING); - if (credentials.getPrivateKey() == null && credentials.getSecretKey() == null) { - log.error(getFriendlyName() - + " Metadata Signing credentials is not found or contains no PrivateKey."); - throw new CredentialsNotAvailableException("config.27", - new Object[] { getFriendlyName() + " Assertion Signing credentials (Alias: " - + getMetadataKeyAlias() + ") is not found or contains no PrivateKey." }); - - } + credentials.setSignatureAlgorithmForSigning(Saml2Utils.getSignatureAlgorithm( + credentials, + basicConfig.getBasicConfiguration( + PvpConstants.CONFIG_PROP_SEC_SIGNING_RSA_ALG, + PvpConstants.DEFAULT_SIGNING_METHODE_RSA), + basicConfig.getBasicConfiguration( + PvpConstants.CONFIG_PROP_SEC_SIGNING_RSA_ALG, + PvpConstants.DEFAULT_SIGNING_METHODE_EC))); return credentials; - } catch (final Exception e) { - log.error("Failed to generate " + getFriendlyName() + " Metadata Signing credentials", e); - throw new CredentialsNotAvailableException("config.27", new Object[] { e.getMessage() }, e); + + } catch (final SamlSigningException e) { + throw new CredentialsNotAvailableException("internal.pvp.01", + new Object[] { getFriendlyName(), getMetadataKeyAlias() }, e); + } } @@ -154,25 +162,27 @@ public abstract class AbstractCredentialProvider { * @return Credentials * @throws CredentialsNotAvailableException In case of a credential error */ - public X509Credential getIdpAssertionSigningCredential() throws CredentialsNotAvailableException { + public EaafX509Credential getIdpAssertionSigningCredential() throws CredentialsNotAvailableException { try { final EaafKeyStoreX509CredentialAdapter credentials = new EaafKeyStoreX509CredentialAdapter(keyStore, - getSignatureKeyAlias(), getSignatureKeyPassword().toCharArray()); + getSignatureKeyAlias(), getSignatureKeyPassword().toCharArray(), getFriendlyName()); credentials.setUsageType(UsageType.SIGNING); - if (credentials.getPrivateKey() == null && credentials.getSecretKey() == null) { - log.error(getFriendlyName() - + " Assertion Signing credentials is not found or contains no PrivateKey."); - throw new CredentialsNotAvailableException("config.27", - new Object[] { getFriendlyName() + " Assertion Signing credentials (Alias: " - + getSignatureKeyAlias() + ") is not found or contains no PrivateKey." }); - - } + credentials.setSignatureAlgorithmForSigning(Saml2Utils.getSignatureAlgorithm( + credentials, + basicConfig.getBasicConfiguration( + PvpConstants.CONFIG_PROP_SEC_SIGNING_RSA_ALG, + PvpConstants.DEFAULT_SIGNING_METHODE_RSA), + basicConfig.getBasicConfiguration( + PvpConstants.CONFIG_PROP_SEC_SIGNING_RSA_ALG, + PvpConstants.DEFAULT_SIGNING_METHODE_EC))); return credentials; + } catch (final Exception e) { - log.error("Failed to generate " + getFriendlyName() + " Assertion Signing credentials", e); - throw new CredentialsNotAvailableException("config.27", new Object[] { e.getMessage() }, e); + throw new CredentialsNotAvailableException("internal.pvp.01", + new Object[] { getFriendlyName(), getSignatureKeyAlias() }, e); + } } @@ -182,34 +192,18 @@ public abstract class AbstractCredentialProvider { * @return Credentials * @throws CredentialsNotAvailableException In case of a credential error */ - public X509Credential getIdpAssertionEncryptionCredential() + public EaafX509Credential getIdpAssertionEncryptionCredential() throws CredentialsNotAvailableException { - try { - // if no encryption key is configured return null - if (StringUtils.isEmpty(getEncryptionKeyAlias())) { - return null; - } - - final EaafKeyStoreX509CredentialAdapter credentials = new EaafKeyStoreX509CredentialAdapter(keyStore, - getEncryptionKeyAlias(), getEncryptionKeyPassword().toCharArray()); - - credentials.setUsageType(UsageType.ENCRYPTION); - - if (credentials.getPrivateKey() == null && credentials.getSecretKey() == null) { - log.error(getFriendlyName() - + " Assertion Encryption credentials is not found or contains no PrivateKey."); - throw new CredentialsNotAvailableException("config.27", - new Object[] { getFriendlyName() + " Assertion Encryption credentials (Alias: " - + getEncryptionKeyAlias() + ") is not found or contains no PrivateKey." }); - - } + // if no encryption key is configured return null + if (StringUtils.isEmpty(getEncryptionKeyAlias())) { + return null; + } - return credentials; + final EaafKeyStoreX509CredentialAdapter credentials = new EaafKeyStoreX509CredentialAdapter(keyStore, + getEncryptionKeyAlias(), getEncryptionKeyPassword().toCharArray(), getFriendlyName()); + credentials.setUsageType(UsageType.ENCRYPTION); + return credentials; - } catch (final Exception e) { - log.error("Failed to generate " + getFriendlyName() + " Assertion Encryption credentials", e); - throw new CredentialsNotAvailableException("config.27", new Object[] { e.getMessage() }, e); - } } /** @@ -218,6 +212,7 @@ public abstract class AbstractCredentialProvider { * @param credentials Credentials for signing * @return OpenSAML Signature object */ + @Deprecated public static Signature getIdpSignature(final Credential credentials) { final PrivateKey privatekey = credentials.getPrivateKey(); final Signature signer = Saml2Utils.createSamlObject(Signature.class); @@ -250,7 +245,7 @@ public abstract class AbstractCredentialProvider { if (keyStore == null) { throw new EaafConfigurationException("module.00", - new Object[] {getFriendlyName(), "KeyStore initialization failed. Maybe wrong password"}); + new Object[] { getFriendlyName(), "KeyStore initialization failed. Maybe wrong password" }); } diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/Saml2Utils.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/Saml2Utils.java index 1a282b55..8b1b041b 100644 --- a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/Saml2Utils.java +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/utils/Saml2Utils.java @@ -20,8 +20,13 @@ package at.gv.egiz.eaaf.modules.pvp2.impl.utils; import java.io.IOException; +import java.security.PrivateKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; import java.util.List; +import javax.annotation.Nonnull; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -31,35 +36,60 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.validation.Schema; import javax.xml.validation.Validator; +import at.gv.egiz.eaaf.core.impl.utils.Random; +import at.gv.egiz.eaaf.modules.pvp2.PvpConstants; +import at.gv.egiz.eaaf.modules.pvp2.api.credential.EaafX509Credential; +import at.gv.egiz.eaaf.modules.pvp2.api.reqattr.EaafRequestedAttribute; +import at.gv.egiz.eaaf.modules.pvp2.exception.SamlSigningException; + +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.opensaml.common.xml.SAMLSchemaBuilder; -import org.opensaml.saml2.core.Attribute; -import org.opensaml.saml2.core.Status; -import org.opensaml.saml2.core.StatusCode; -import org.opensaml.saml2.metadata.AssertionConsumerService; -import org.opensaml.saml2.metadata.SPSSODescriptor; -import org.opensaml.ws.soap.soap11.Body; -import org.opensaml.ws.soap.soap11.Envelope; -import org.opensaml.xml.XMLObject; -import org.opensaml.xml.XMLObjectBuilderFactory; -import org.opensaml.xml.io.Marshaller; -import org.opensaml.xml.io.MarshallingException; -import org.opensaml.xml.schema.XSString; -import org.opensaml.xml.schema.impl.XSStringBuilder; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.XMLObjectBuilderFactory; +import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.core.xml.io.Marshaller; +import org.opensaml.core.xml.io.MarshallingException; +import org.opensaml.core.xml.schema.XSString; +import org.opensaml.core.xml.schema.impl.XSStringBuilder; +import org.opensaml.saml.common.SAMLObjectContentReference; +import org.opensaml.saml.common.xml.SAMLSchemaBuilder; +import org.opensaml.saml.common.xml.SAMLSchemaBuilder.SAML1Version; +import org.opensaml.saml.saml2.core.Attribute; +import org.opensaml.saml.saml2.core.Status; +import org.opensaml.saml.saml2.core.StatusCode; +import org.opensaml.saml.saml2.metadata.AssertionConsumerService; +import org.opensaml.saml.saml2.metadata.SPSSODescriptor; +import org.opensaml.security.SecurityException; +import org.opensaml.security.x509.X509Credential; +import org.opensaml.soap.soap11.Body; +import org.opensaml.soap.soap11.Envelope; +import org.opensaml.xmlsec.SecurityConfigurationSupport; +import org.opensaml.xmlsec.SignatureSigningConfiguration; +import org.opensaml.xmlsec.keyinfo.KeyInfoGenerator; +import org.opensaml.xmlsec.keyinfo.KeyInfoGeneratorFactory; +import org.opensaml.xmlsec.keyinfo.KeyInfoGeneratorManager; +import org.opensaml.xmlsec.keyinfo.NamedKeyInfoGeneratorManager; +import org.opensaml.xmlsec.keyinfo.impl.BasicKeyInfoGeneratorFactory; +import org.opensaml.xmlsec.signature.KeyInfo; +import org.opensaml.xmlsec.signature.SignableXMLObject; +import org.opensaml.xmlsec.signature.Signature; +import org.opensaml.xmlsec.signature.support.ContentReference; +import org.opensaml.xmlsec.signature.support.SignatureConstants; +import org.opensaml.xmlsec.signature.support.SignatureException; +import org.opensaml.xmlsec.signature.support.Signer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; -import at.gv.egiz.eaaf.core.impl.utils.Random; -import at.gv.egiz.eaaf.modules.pvp2.PvpConstants; -import at.gv.egiz.eaaf.modules.pvp2.api.reqattr.EaafRequestedAttribute; - public class Saml2Utils { private static final Logger log = LoggerFactory.getLogger(Saml2Utils.class); private static DocumentBuilder builder; + private static SAMLSchemaBuilder schemaBuilder; static { + schemaBuilder = new SAMLSchemaBuilder(SAML1Version.SAML_11); + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); @@ -73,16 +103,144 @@ public class Saml2Utils { } /** + * Sign a OpenSAML 3.x object with a {@link X509Credential}. + * <br> + * <p>This method used {@link PvpConstants.DEFAULT_SIGNING_METHODE_RSA} + * or {@link PvpConstants.DEFAULT_SIGNING_METHODE_EC} as algorithm</p> + * + * @param <T> {@link SignableXMLObject} + * @param toSign object that should be signed + * @param signingCredential Credentials that should be used for signing + * @param injectCertificate true, if certificate should be part of the signature + * @return Signed object + * @throws SamlSigningException In case of a signing error + */ + public static <T extends SignableXMLObject> T signSamlObject(@Nonnull T toSign, + @Nonnull EaafX509Credential signingCredential, boolean injectCertificate) throws SamlSigningException { + + try { + final String usedSigAlg = getSignatureAlgorithm(signingCredential, + PvpConstants.DEFAULT_SIGNING_METHODE_RSA, + PvpConstants.DEFAULT_SIGNING_METHODE_EC); + + final Signature signature = createSignature(signingCredential, usedSigAlg, injectCertificate); + toSign.setSignature(signature); + + final String digestAlgorithm = getDigestAlgorithm(usedSigAlg); + final List<ContentReference> contentReferences = signature.getContentReferences(); + if (!CollectionUtils.isEmpty(contentReferences)) { + ((SAMLObjectContentReference) contentReferences.get(0)).setDigestAlgorithm(digestAlgorithm); + + } else { + log.error("Unable to set DigestMethodAlgorithm - algorithm {} not set", digestAlgorithm); + + } + + log.trace("Marshall samlToken."); + XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(toSign).marshall(toSign); + + log.trace("Sign samlToken."); + Signer.signObject(signature); + + return toSign; + + } catch (final SignatureException | MarshallingException | SecurityException e) { + throw new SamlSigningException("internal.pvp.96", + new Object[] {signingCredential.getEntityId(), e.getMessage()}, e); + + } + + } + + /** + * Select signature algorithm for a given credential. + * + * @param credentials {@link X509Credential} that will be used for signing + * @param rsaSigAlgorithm RSA based signing algorithm that should be used in case of RSA credential + * @param ecSigAlgorithm EC based signing algorithm that should be used in case of RSA credential + * @return either the rsaSigAlgorithm or the ecSigAlgorithm + * @throws SamlSigningException In case of an unsupported credential + */ + public static String getSignatureAlgorithm(X509Credential credentials, + String rsaSigAlgorithm, String ecSigAlgorithm) throws SamlSigningException { + final PrivateKey privatekey = credentials.getPrivateKey(); + if (privatekey instanceof RSAPrivateKey) { + return rsaSigAlgorithm; + + } else if (privatekey instanceof ECPrivateKey) { + return ecSigAlgorithm; + + } else { + log.warn("Could NOT evaluate the Private-Key type from " + credentials.getEntityId() + + " credential."); + throw new SamlSigningException("internal.pvp.97", + new Object[] {credentials.getEntityId(), privatekey.getClass().getName()}); + + } + } + + /** + * Select a digest algorithm for a already selected signing algorithm. + * + * @param signatureAlgorithmName Signing algorithm that will be used + * @return Digest algorithm identifier + */ + public static String getDigestAlgorithm(String signatureAlgorithmName) { + if (StringUtils.isBlank(signatureAlgorithmName)) { + return PvpConstants.DEFAULT_DIGESTMETHODE; + } + + final String canonicalAlgorithm = signatureAlgorithmName.trim(); + final String digestAlgorithm = PvpConstants.SIGNATURE_TO_DIGEST_ALGORITHM_MAP.get(canonicalAlgorithm); + if (null != digestAlgorithm) { + return digestAlgorithm; + + } + + log.warn("Signing algorithm: {} does not contain a known digist algorithm. Use: {} as default", + signatureAlgorithmName, PvpConstants.DEFAULT_DIGESTMETHODE); + return PvpConstants.DEFAULT_DIGESTMETHODE; + + } + + /** + * Get a {@link KeyInfoGenerator} that injects key information into XML signature. + * + * @param credential @link X509Credential} that will be used for signing + * @param injectCertificate Set <code>true</code> if the certificate should be added to KeyInfo + * @return Generator for a XML signature key-information + */ + public static KeyInfoGenerator getKeyInfoGenerator(X509Credential credential, boolean injectCertificate) { + //OpenSAML3 only support RSA and DSA for direct key injection + KeyInfoGeneratorFactory keyInfoGenFac = null; + if (injectCertificate || credential.getPublicKey() instanceof ECPublicKey) { + final SignatureSigningConfiguration secConfiguration = SecurityConfigurationSupport + .getGlobalSignatureSigningConfiguration(); + final NamedKeyInfoGeneratorManager keyInfoManager = secConfiguration.getKeyInfoGeneratorManager(); + final KeyInfoGeneratorManager keyInfoGenManager = keyInfoManager.getDefaultManager(); + keyInfoGenFac = keyInfoGenManager.getFactory(credential); + + } else { + keyInfoGenFac = createKeyInfoWithoutCertificate(credential); + + } + + return keyInfoGenFac.newInstance(); + + } + + + /** * Create a SAML2 object. * - * @param <T> SAML2 object class + * @param <T> SAML2 object class * @param clazz object class * @return SAML2 object */ public static <T> T createSamlObject(final Class<T> clazz) { try { final XMLObjectBuilderFactory builderFactory = - org.opensaml.xml.Configuration.getBuilderFactory(); + XMLObjectProviderRegistrySupport.getBuilderFactory(); final QName defaultElementName = (QName) clazz.getDeclaredField("DEFAULT_ELEMENT_NAME").get(null); @@ -119,7 +277,7 @@ public class Saml2Utils { throws IOException, MarshallingException, TransformerException { final Document document = builder.newDocument(); final Marshaller out = - org.opensaml.xml.Configuration.getMarshallerFactory().getMarshaller(object); + XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); out.marshall(object, document); return document; } @@ -132,7 +290,7 @@ public class Saml2Utils { public static Status getSuccessStatus() { final Status status = Saml2Utils.createSamlObject(Status.class); final StatusCode statusCode = Saml2Utils.createSamlObject(StatusCode.class); - statusCode.setValue(StatusCode.SUCCESS_URI); + statusCode.setValue(StatusCode.SUCCESS); status.setStatusCode(statusCode); return status; } @@ -165,7 +323,7 @@ public class Saml2Utils { * @return */ public static Envelope buildSoap11Envelope(final XMLObject payload) { - final XMLObjectBuilderFactory bf = org.opensaml.xml.Configuration.getBuilderFactory(); + final XMLObjectBuilderFactory bf = XMLObjectProviderRegistrySupport.getBuilderFactory(); final Envelope envelope = (Envelope) bf.getBuilder(Envelope.DEFAULT_ELEMENT_NAME) .buildObject(Envelope.DEFAULT_ELEMENT_NAME); final Body body = @@ -211,7 +369,8 @@ public class Saml2Utils { */ public static void schemeValidation(final XMLObject xmlObject) throws Exception { try { - final Schema test = SAMLSchemaBuilder.getSAML11Schema(); + + final Schema test = schemaBuilder.getSAMLSchema(); final Validator val = test.newValidator(); final DOMSource source = new DOMSource(xmlObject.getDOM()); val.validate(source); @@ -227,11 +386,37 @@ public class Saml2Utils { private static XMLObject createAttributeValue(final QName attributeValueType, final String value) { - final XSStringBuilder stringBuilder = (XSStringBuilder) org.opensaml.xml.Configuration + final XSStringBuilder stringBuilder = (XSStringBuilder) XMLObjectProviderRegistrySupport .getBuilderFactory().getBuilder(XSString.TYPE_NAME); final XSString stringValue = stringBuilder.buildObject(attributeValueType, XSString.TYPE_NAME); stringValue.setValue(value); return stringValue; } + + private static Signature createSignature(X509Credential signingCredential, + String usedSigAlg, boolean injectCertificate) + throws SecurityException, SamlSigningException { + log.trace("Generating OpenSAML signature object ... "); + final Signature signature = (Signature) XMLObjectProviderRegistrySupport.getBuilderFactory() + .getBuilder(Signature.DEFAULT_ELEMENT_NAME) + .buildObject(Signature.DEFAULT_ELEMENT_NAME); + signature.setSigningCredential(signingCredential); + signature.setSignatureAlgorithm(usedSigAlg); + final KeyInfo keyInfo = getKeyInfoGenerator(signingCredential, injectCertificate).generate(signingCredential); + signature.setKeyInfo(keyInfo); + signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); + return signature; + + } + + private static KeyInfoGeneratorFactory createKeyInfoWithoutCertificate(X509Credential credential) { + final KeyInfoGeneratorFactory keyInfoGenFac = new BasicKeyInfoGeneratorFactory(); + ((BasicKeyInfoGeneratorFactory)keyInfoGenFac).setEmitPublicKeyValue(true); + ((BasicKeyInfoGeneratorFactory)keyInfoGenFac).setEmitEntityIDAsKeyName(true); + ((BasicKeyInfoGeneratorFactory)keyInfoGenFac).setEmitKeyNames(true); + ((BasicKeyInfoGeneratorFactory)keyInfoGenFac).setEmitPublicDEREncodedKeyValue(true); + return keyInfoGenFac; + } + } |