Skip to content

Commit

Permalink
SES-85, SES-88
Browse files Browse the repository at this point in the history
Added support for metadata XML signature verification and configuration in ExtendedMetadataDelegate
Added support for MetaIOP and PKIX checks on signatures and added extended metadata options
Trust engine for verification of signatures is configured for either MetaIOP or PKIX based on configuration
Generated metadata now contains whole certificate path of credentials
Changed initialization of key store
Metadata filter now uses correct content-type as defined in spec
Providers which fail validation are not added to available list anymore, but are retried on next refresh
New JUnits
Metadata UI generation has new options and uses default "true" values for metadata signing and authnrequests signed options
  • Loading branch information
vschafer committed Apr 3, 2011
1 parent f6eeb7c commit 5f424cb
Show file tree
Hide file tree
Showing 41 changed files with 2,282 additions and 452 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@
*/
package org.springframework.security.saml;

import org.opensaml.Configuration;
import org.opensaml.PaosBootstrap;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.parse.ParserPool;
import org.opensaml.xml.security.keyinfo.KeyInfoGeneratorFactory;
import org.opensaml.xml.security.keyinfo.NamedKeyInfoGeneratorManager;
import org.opensaml.xml.security.x509.X509KeyInfoGeneratorFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.access.BootstrapException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
Expand All @@ -41,9 +45,23 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
PaosBootstrap.bootstrap();
ParserPool pool = beanFactory.getBean(ParserPool.class);
new ParserPoolHolder(pool);
setMetadataKeyInfoGenerator();
} catch (ConfigurationException e) {
throw new BootstrapException("Error invoking OpenSAML bootrap", e);
}
}

/**
* Method registers extension specific KeyInfoGenerator which emits .
*
* @see SAMLConstants#SAML_METADATA_KEY_INFO_GENERATOR
*/
protected void setMetadataKeyInfoGenerator() {
NamedKeyInfoGeneratorManager manager = Configuration.getGlobalSecurityConfiguration().getKeyInfoGeneratorManager();
X509KeyInfoGeneratorFactory generator = new X509KeyInfoGeneratorFactory();
generator.setEmitEntityCertificate(true);
generator.setEmitEntityCertificateChain(true);
manager.registerFactory(SAMLConstants.SAML_METADATA_KEY_INFO_GENERATOR, generator);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@
* @author Vladimir Schaefer
*/
public class SAMLConstants {


/**
* Constant identifying special version of the KeyInfoGenerator used to include credentials in generated
* metadata.
*/
public static final String SAML_METADATA_KEY_INFO_GENERATOR = "MetadataKeyInfoGenerator";

public static final String AUTH_N_REQUEST = "AuthNRequest";
public static final String AUTH_N_RESPONSE = "AuthNResponse";
public static final String LOGOUT_REQUEST = "LogoutRequest";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import org.opensaml.saml2.metadata.RoleDescriptor;
import org.opensaml.saml2.metadata.SPSSODescriptor;
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
import org.opensaml.security.MetadataCredentialResolver;
import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
import org.opensaml.ws.transport.http.HttpServletResponseAdapter;
import org.opensaml.xml.Configuration;
Expand All @@ -33,13 +32,17 @@
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.security.keyinfo.KeyInfoCredentialResolver;
import org.opensaml.xml.security.keyinfo.StaticKeyInfoCredentialResolver;
import org.opensaml.xml.signature.SignatureTrustEngine;
import org.opensaml.xml.signature.impl.ExplicitKeySignatureTrustEngine;
import org.opensaml.xml.signature.impl.PKIXSignatureTrustEngine;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.saml.SAMLCredential;
import org.springframework.security.saml.key.KeyManager;
import org.springframework.security.saml.metadata.ExtendedMetadata;
import org.springframework.security.saml.metadata.MetadataManager;
import org.springframework.security.saml.trust.MetadataCredentialResolver;
import org.springframework.security.saml.trust.PKIXInformationResolver;
import org.springframework.util.Assert;

import javax.servlet.ServletException;
Expand All @@ -66,6 +69,8 @@ public class SAMLContextProviderImpl implements SAMLContextProvider, Initializin

protected KeyManager keyManager;
protected MetadataManager metadata;
protected MetadataCredentialResolver metadataResolver;
protected PKIXInformationResolver pkixResolver;

/**
* Creates a SAMLContext with local entity values filled. Also request and response must be stored in the context
Expand Down Expand Up @@ -235,6 +240,12 @@ private void populateLocalEntity(SAMLMessageContext samlContext) throws Metadata

}

/**
* Populates a decrypter based on settings in the extended metadata or using a default credential when no
* encryption credential is specified in the extended metadata.
*
* @param samlContext context to populate decryptor for.
*/
protected void populateDecrypter(SAMLMessageContext samlContext) {

// Locate encryption key for this entity
Expand All @@ -249,19 +260,29 @@ protected void populateDecrypter(SAMLMessageContext samlContext) {
// Extracts EncryptedKey from the encrypted XML using the encryptedKeyResolver and attempts to decrypt it
// using private keys supplied by the resolver.
KeyInfoCredentialResolver resolver = new StaticKeyInfoCredentialResolver(encryptionCredential);

Decrypter decrypter = new Decrypter(null, resolver, encryptedKeyResolver);
decrypter.setRootInNewDocument(true);

samlContext.setLocalDecrypter(decrypter);

}

/**
* Based on the settings in the extended metadata either creates a PKIX trust engine with trusted keys specified
* in the extended metadata as anchors or (by default) an explicit trust engine using data from the metadata or
* from the values overriden in the ExtendedMetadata.
*
* @param samlContext context to populate
*/
protected void populateTrustEngine(SAMLMessageContext samlContext) {

MetadataCredentialResolver credentialResolver = new MetadataCredentialResolver(metadata);
ExplicitKeySignatureTrustEngine trustEngine = new ExplicitKeySignatureTrustEngine(credentialResolver, Configuration.getGlobalSecurityConfiguration().getDefaultKeyInfoCredentialResolver());
samlContext.setLocalTrustEngine(trustEngine);

SignatureTrustEngine engine;
if ("pkix".equalsIgnoreCase(samlContext.getLocalExtendedMetadata().getSecurityProfile())) {
engine = new PKIXSignatureTrustEngine(pkixResolver, Configuration.getGlobalSecurityConfiguration().getDefaultKeyInfoCredentialResolver());
} else {
engine = new ExplicitKeySignatureTrustEngine(metadataResolver, Configuration.getGlobalSecurityConfiguration().getDefaultKeyInfoCredentialResolver());
}
samlContext.setLocalTrustEngine(engine);
}

@Autowired
Expand All @@ -275,13 +296,18 @@ public void setKeyManager(KeyManager keyManager) {
}

/**
* Verifies that required entities were autowired or set.
* Verifies that required entities were autowired or set and initializes resolvers used to construct trust engines.
*
* @throws javax.servlet.ServletException
*/
public void afterPropertiesSet() throws ServletException {

Assert.notNull(keyManager, "Key manager must be set");
Assert.notNull(metadata, "Metadata must be set");

metadataResolver = new MetadataCredentialResolver(metadata, keyManager);
pkixResolver = new PKIXInformationResolver(metadataResolver, metadata, keyManager);

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,55 +14,91 @@
*/
package org.springframework.security.saml.key;

import org.opensaml.common.SAMLRuntimeException;
import org.opensaml.xml.security.CriteriaSet;
import org.opensaml.xml.security.SecurityException;
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.security.credential.CredentialResolver;
import org.opensaml.xml.security.credential.KeyStoreCredentialResolver;
import org.opensaml.xml.security.criteria.EntityIDCriteria;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;

import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* Serves as a wrapper for java security KeyStore of JKS type which can be conveniently initialized as a Spring bean.
* <p>
* The instance can be inserted into springConfiguration in a following manner:
* <pre>
* <bean id="keyStore" class="org.springframework.security.saml.key.JKSKeyManager">
* <constructor-arg index="0" value="d:/keystore.jks" />
* <constructor-arg index="1" value="nalle123" />
* </bean>
* </pre>
* Instances of java.security.KeyStore can then be obtained by calls to the getKeyStore method:
* <pre>
* <constructor-arg index="0">
* <bean factory-bean="keyStore" factory-method="getKeyStore" />
* </constructor-arg>
* </pre>
* </p>
* <p/>
* Class also provides convenience methods for loading of certificates and public keys.
* Class provides access to private and trusted keys for SAML Extension configuration. Keys are stored in the underlaying
* KeyStore object. Class also provides additional convenience methods for loading of certificates and public keys.
*
* @author Vladimir Schafer
*/
public class JKSKeyManager {
public class JKSKeyManager implements KeyManager {

private final Logger log = LoggerFactory.getLogger(JKSKeyManager.class);

private CredentialResolver credentialResolver;
private KeyStore keyStore;
private Set<String> availableKeys;
private String defaultKey;

/**
* Keystore to retrieve keys from
* Default constructor which uses an existing KeyStore instance for loading of credentials. Available keys are
* calculated automatically.
*
* @param keyStore key store to use
* @param passwords passwords used to access private keys
* @param defaultKey default key
*/
private KeyStore ks;
public JKSKeyManager(KeyStore keyStore, Map<String, String> passwords, String defaultKey) {
this.keyStore = keyStore;
this.availableKeys = getAvailableKeys(keyStore);
this.credentialResolver = new KeyStoreCredentialResolver(keyStore, passwords);
this.defaultKey = defaultKey;
}

/**
* Default constructor.
* Default constructor which instantiates a new KeyStore used to load all credentials. Available keys are
* calculated automatically.
*
* @param storeFile file pointing to the JKS keystore
* @param storePass password to access the keystore
* @param passwords passwords used to access private keys
* @param defaultKey default key
*/
public JKSKeyManager(Resource storeFile, String storePass) {
initialize(storeFile, storePass, "JKS");
public JKSKeyManager(Resource storeFile, String storePass, Map<String, String> passwords, String defaultKey) {
this.keyStore = initialize(storeFile, storePass, "JKS");
this.availableKeys = getAvailableKeys(keyStore);
this.credentialResolver = new KeyStoreCredentialResolver(keyStore, passwords);
this.defaultKey = defaultKey;
}

/**
* Loads all aliases available in the keyStore.
*
* @param keyStore key store to load aliases from
* @return aliases
*/
private Set<String> getAvailableKeys(KeyStore keyStore) {
try {
Set<String> availableKeys = new HashSet<String>();
Enumeration<String> aliases = keyStore.aliases();
while (aliases.hasMoreElements()) {
availableKeys.add(aliases.nextElement());
}
return availableKeys;
} catch (KeyStoreException e) {
throw new RuntimeException("Unable to load aliases from keyStore", e);
}
}

/**
Expand All @@ -71,13 +107,15 @@ public JKSKeyManager(Resource storeFile, String storePass) {
* @param storeFile file pointing to the JKS keystore
* @param storePass password to open the keystore
* @param storeType type of keystore
* @return initialized key store
*/
private void initialize(Resource storeFile, String storePass, String storeType) {
private KeyStore initialize(Resource storeFile, String storePass, String storeType) {
InputStream inputStream = null;
try {
inputStream = storeFile.getInputStream();
ks = KeyStore.getInstance(storeType);
KeyStore ks = KeyStore.getInstance(storeType);
ks.load(inputStream, storePass.toCharArray());
return ks;
} catch (Exception e) {
log.error("Error initializing key store", e);
throw new RuntimeException("Error initializing keystore", e);
Expand All @@ -96,15 +134,14 @@ private void initialize(Resource storeFile, String storePass, String storeType)
* Returns certificate with the given alias from the keystore.
*
* @param alias alias of certificate to find
*
* @return certificate with the given alias or null if not found
*/
public X509Certificate getCertificate(String alias) {
if (alias == null || alias.length() == 0) {
return null;
}
try {
return (X509Certificate) ks.getCertificate(alias);
return (X509Certificate) keyStore.getCertificate(alias);
} catch (Exception e) {
log.error("Error loading certificate", e);
}
Expand All @@ -115,7 +152,6 @@ public X509Certificate getCertificate(String alias) {
* Returns public key with the given alias
*
* @param alias alias of the key to find
*
* @return public key of the alias or null if not found
*/
public PublicKey getPublicKey(String alias) {
Expand All @@ -127,11 +163,58 @@ public PublicKey getPublicKey(String alias) {
}
}

public Iterable<Credential> resolve(CriteriaSet criteriaSet) throws org.opensaml.xml.security.SecurityException {
return credentialResolver.resolve(criteriaSet);
}

public Credential resolveSingle(CriteriaSet criteriaSet) throws SecurityException {
return credentialResolver.resolveSingle(criteriaSet);
}

/**
* @return returns the initialized key store
* Returns Credential object used to sign the messages issued by this entity.
* Public, X509 and Private keys are set in the credential.
*
* @param keyName name of the key to use, in case of null default key is used
* @return credential
*/
public Credential getCredential(String keyName) {

if (keyName == null) {
keyName = defaultKey;
}

try {
CriteriaSet cs = new CriteriaSet();
EntityIDCriteria criteria = new EntityIDCriteria(keyName);
cs.add(criteria);
return resolveSingle(cs);
} catch (org.opensaml.xml.security.SecurityException e) {
throw new SAMLRuntimeException("Can't obtain SP signing key", e);
}

}

/**
* Returns Credential object used to sign the messages issued by this entity.
* Public, X509 and Private keys are set in the credential.
*
* @return credential
*/
public Credential getDefaultCredential() {
return getCredential(null);
}

public String getDefaultCredentialName() {
return defaultKey;
}

public Set<String> getAvailableCredentials() {
return availableKeys;
}

public KeyStore getKeyStore() {
return ks;
return keyStore;
}

}
}
Loading

0 comments on commit 5f424cb

Please sign in to comment.