Search code examples
springspring-securitysamlsaml-2.0spring-security-saml2

Spring Secuirty SAML2 Core - Add a key for an Identity Provider created on runtime


Currently I am editing existing implementation of SAML support on my project using Spring Security. I have multiple IdentityProviders, for which I store data in the database. Using my app UI I can add new IdentityProviders on runtime, which will be added to CachingMetadataManager. After that, refreshMetadata is called. However I have JKSKeyManager, which is loaded on app startup and loads a single JKS keystore which is used for all metadatas for all IdentityProviders. I want a user to be able to upload (or paste) a private key using my app UI during IdentityProvider creation on runtime, so that, different key can be used for different IdentityProvider, but I don't know how. There's no difference for me if I store the keys in JKS file or somewhere else. There's no spring boot and I am afraid there is no ability to upgrade the library versions/migrate to other libraries.

The key manager injection looks like this:

@Bean
  public KeyManager keyManager() {
    DefaultResourceLoader loader = new DefaultResourceLoader();
    Resource storeFile = loader
        .getResource(environment.getProperty("server.ssl.key-store"));
    Map<String, String> passwords = new HashMap<>();
    passwords.put(environment.getProperty("server.ssl.key-alias"), environment.getProperty("server.ssl.key-store-password"));
    String defaultKey = "spring";
    return new JKSKeyManager(storeFile, environment.getProperty("server.ssl.key-store-password"), passwords, defaultKey);
  }

SAML extension used is spring-security-saml2-core (1.0.3.RELEASE) from org.springframework.security.extensions. And Spring Security vesion is 3.2.9.RELEASE.


Solution

  • Since JKSKeyManager computes all the available keys at construction time, a custom implementation of KeyManager would likely be best.

    Something like the following, for example:

    public DynamicJKSKeyManager extends JKSKeyManager {
        private final KeyStore keyStore;
    
        public KeyStoreKeyManager(KeyStore keyStore, Map<String, String> passwords, String defaultKey) {
            super(keyStore, passwords, defaultKey);
            this.keyStore = keyStore;
        }
    
        @Override
        public Set<String> getAvailableCredentials() {
            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);
            }
        }
    }
    

    would change the getAvailableCredentials method to read the KeyStore aliases on each invocation.

    Then, when you need to add a key to the KeyStore, you can use the KeyStore API to do it.

    Of course, as you mentioned, you don't have to use KeyStore. Wherever you store your keys, you can implement your own KeyManager and use OpenSAML's CollectionCredentialResolver instead of KeyStoreCredentialResolver.