Expected Behavior Ability to do something like this:

RelyingPartyRegistration.Builder builder;
KeyStore keyStore;

Credential credential = new KeyStoreX509CredentialAdapter(keyStore, "alias", "password".toCharArray());
builder.decryptionX509Credentials(creds -> creds.add(credential))
builder.signingX509Credentials(creds -> creds.add(credential))

Current Behavior Currently have to do something like this:

RelyingPartyRegistration.Builder builder;
KeyStore keyStore;

X509Credential credential = new KeyStoreX509CredentialAdapter(keyStore, "alias", "password".toCharArray());
Saml2X509Credential samlCred = new Saml2X509Credential(
        credential.getPrivateKey(),
        credential.getEntityCertificate(),
        Saml2X509Credential.Saml2X509CredentialType.DECRYPTION,
        Saml2X509Credential.Saml2X509CredentialType.SIGNING
);
builder.decryptionX509Credentials(creds -> creds.add(samlCred));
builder.signingX509Credentials(creds -> creds.add(samlCred));

Context

OpenSAML provides org.opensaml.security.credential.Credential and multiple implementations to cover various useful cases. Spring Security instead provides org.springframework.security.saml2.core.Saml2X509Credential with much more restricted functionality. However, internally Spring just uses the Saml2X509Credential to build a Credential.

Comment From: OrangeDog

This is similar to https://github.com/spring-projects/spring-boot/issues/40610 but they're probably orthogonal issues.

Comment From: jzheaux

In order for Spring Security to use OpenSAML's Credential interally, I think it would be best to introduce an interface for SamlX509Credential. I'm not certain on the name, but Saml2X509CredentialAccessor might work.

I propose doing the following:

  1. Add the interface
  2. Provide another implementation that holds and adapts OpenSAML's X509Credential
  3. Add the appropriate methods to RelyingPartyRegistration that use the new interface
  4. Update OpenSaml5Template and OpenSaml4Template to check for this implementation and extract the underlying X509Credential accordingly. The last one might look something like this:
private static BasicX509Credential fromSaml2X509Credential(Saml2X509CredentialAccessor key, String entityId) {
    if (key instanceof OpenSamlX509Credential credential) {
        return credential.getX509Credential();
    }
    BasicX509Credential cred = CredentialSupport.getSimpleCredential(key.getCertificate(), key.getPrivateKey());
    cred.setEntityId(entityId);
    return cred;
}

Comment From: therepanic

Hi, @jzheaux! I guess we would also need to implement current Saml2X509Credential from Saml2X509CredentialAccessor? Also we would have to completely replace Saml2X509Credential with Saml2X509CredentialAccessor in RelyingPartyRegistration? You have described the instruction completely clear and if you don't mind you can assign me and I can do it.

Comment From: OrangeDog

In order for Spring Security to use OpenSAML's Credential inter[n]ally

To be clear, it already does. But no API is exposed for it, so you currently have to convert it to something else first, and then Spring Security converts it straight back.

The goal here is to use a KeyStore for SAML credentials in as little code as possible. Both Spring Boot and OpenSAML provide helpful utilities for loading keys from anywhere you could think of, but Spring Security SAML does not accept any of their outputs.