Bug description I'm integrating with an external IdP. This IdP wants the AuthnRequest signed and, moreover, needs the KeyInfo present in the Signature but KeyInfo is not added and we have verification failure

To Reproduce Use this configuration:

    @Bean
    public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception{
        KeyStore ks = KeyStore.getInstance("PKCS12");
        char[] pwd = "applicazione.mock".toCharArray();
        Resource keystoreRes = new ClassPathResource("applicazione.mock.jks");
        ks.load(keystoreRes.getInputStream(), pwd);
        PrivateKey privateKey = (PrivateKey)ks.getKey(keyStoreAlias, keyStoreKeyPassword.toCharArray());
        X509Certificate cert = (X509Certificate) ks.getCertificate(keyStoreAlias);

        RelyingPartyRegistration registration = RelyingPartyRegistrations
                .fromMetadataLocation(assertingPartyMetadataLocation)
                .registrationId(registrationId)
                .entityId(spEntityId)
                .signingX509Credentials((c) -> c.add(Saml2X509Credential.signing(privateKey, cert)))
                .decryptionX509Credentials((c)->c.add(Saml2X509Credential.decryption(privateKey, cert)))
                .build();
        return new InMemoryRelyingPartyRegistrationRepository(registration);
    }

And check it with an IdP who needs AuthnRequest signed and needs the KeyInfo element present.

A verification error will be raised.

I attach the loaded JKS containing the private key and certificate (self-signed) the IdP metadata (it's a fake IdP) and the generated AuthnRequest; its values are in the previous code I was expecting the KeyInfo was present, instead it is not present applicazione.mock.zip

idp-metadata.zip GeneratedAuthnRequest.zip

Comment From: angeloimm

I solved my current issue. Anyway I think it's a my mistake. Basically I modified the org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationRequestFactory I added the following method:

    private KeyInfoGenerator x509KeyInfoGenerator() {
        X509KeyInfoGeneratorFactory generator = new X509KeyInfoGeneratorFactory();
        generator.setEmitEntityCertificate(true);
        generator.setEmitEntityCertificateChain(true);
        return generator.newInstance();
    }

I called this method here:

    private SignatureSigningParameters resolveSigningParameters(RelyingPartyRegistration relyingPartyRegistration) {
        List<Credential> credentials = resolveSigningCredentials(relyingPartyRegistration);
        List<String> algorithms = Collections.singletonList(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
        List<String> digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256);
        String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS;
        SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver();
        CriteriaSet criteria = new CriteriaSet();
        BasicSignatureSigningConfiguration signingConfiguration = new BasicSignatureSigningConfiguration();
        signingConfiguration.setSigningCredentials(credentials);
        signingConfiguration.setSignatureAlgorithms(algorithms);
        signingConfiguration.setSignatureReferenceDigestMethods(digests);
        signingConfiguration.setSignatureCanonicalizationAlgorithm(canonicalization);
        criteria.add(new SignatureSigningConfigurationCriterion(signingConfiguration));
        try {
            SignatureSigningParameters parameters = resolver.resolveSingle(criteria);
            parameters.setKeyInfoGenerator(x509KeyInfoGenerator());
            Assert.notNull(parameters, "Failed to resolve any signing credential");
            return parameters;
        }
        catch (Exception ex) {
            throw new Saml2Exception(ex);
        }
    }

Now I don't have errors on IdP side but I'm thinking I'm missing something in my configuration. This is my whole web security configuration:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, 
securedEnabled = true, 
jsr250Enabled = true)
public class ApplicazioneMockWebSecurityCfg extends WebSecurityConfigurerAdapter {
    static {
        OpenSamlInitializationService.requireInitialize((registry) -> {
            X509KeyInfoGeneratorFactory generator = new X509KeyInfoGeneratorFactory();
            generator.setEmitEntityCertificate(true);
            generator.setEmitEntityCertificateChain(true);
            NamedKeyInfoGeneratorManager manager = new NamedKeyInfoGeneratorManager();
            manager.registerDefaultFactory(generator);

        });
    }
    @Value("${sael.applicazione.mock.external.idp.metadata.location}")
    private String assertingPartyMetadataLocation;
    @Value("${sael.applicazione.mock.external.idp.metadata.registration.id}")
    private String registrationId;
    @Value("${server.ssl.key-alias}")
    private String keyStoreAlias;
    @Value("${server.ssl.key-password}")
    private String keyStoreKeyPassword;
    @Value("${server.ssl.key-store-password}")
    private String keyStorePassword;
    @Value("${server.ssl.keystore}")
    private String keyStoreName;
    @Value("${server.ssl.key-store-type}")
    private String keyStoreType;
    @Value("${sael.spid.service.provider.applicazione.mock.metadata.entity.id}")
    private String spEntityId;
    public static final String LOGOUT_URL = "/public/logout";
    public static final String LOGIN_PAGE = "/public/home";

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        OpenSamlAuthenticationProvider authenticationProvider = new OpenSamlAuthenticationProvider();
        authenticationProvider.setResponseAuthenticationConverter(responseToken -> {
            //            Saml2Authentication authentication = OpenSamlAuthenticationProvider
            //                    .createDefaultResponseAuthenticationConverter() 
            //                    .convert(responseToken);
            Assertion assertion = responseToken.getResponse().getAssertions().get(0);
            String username = assertion.getSubject().getNameID().getValue();
            List<AttributeStatement> attrStatements = assertion.getAttributeStatements();
            String valoreAttributo = null;
            Map<String, String> samlAttributes = new HashMap<>();
            for (AttributeStatement attrStatement : attrStatements) {
                List<Attribute> attrs =  attrStatement.getAttributes();
                for (Attribute attr : attrs) {
                    String nomeAttributo = attr.getName();
                    List<XMLObject> valoriAttributo = attr.getAttributeValues();
                    //In genere la lista dei valori è di 1 elemento
                    XMLObject valueObj = valoriAttributo.get(0);
                    valoreAttributo = getValue(valueObj, valoreAttributo);
                    samlAttributes.put(nomeAttributo, valoreAttributo);
                }
            }
            if( !StringUtils.hasText(valoreAttributo) ) {
                throw new IllegalStateException("Impossibile proseguire. Codice Fiscale non trovato tra gli attributi SAML");
            }

            UserDetails userDetails = new ApplicazioneMockLoggedUser(username, "[PROTECTED]", samlAttributes, AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
            return new SaelSamlAuthentication(userDetails); 
        });
        Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver =
                new DefaultRelyingPartyRegistrationResolver(this.relyingPartyRegistrations());
        http
        .authorizeRequests()
        .antMatchers("/protected/**")
        .authenticated()
        .antMatchers("/public/**")
        .permitAll()
        .and()
        .saml2Login(authorize ->{
            authorize
            .loginPage(LOGIN_PAGE)
            .authenticationManager(new ProviderManager(authenticationProvider))
            ;
        })
        .logout(logout->{
            logout
            .logoutUrl(LOGOUT_URL)
            .logoutSuccessHandler(saelLogoutSuccessHanlder())
            .logoutRequestMatcher(saelRequestMatcher())
            .invalidateHttpSession(true)
            .deleteCookies("JSESSIONID")
            //.logoutSuccessUrl(LOGIN_PAGE+"?logout")
            .permitAll();
        })  
        .addFilterBefore(new Saml2MetadataFilter(relyingPartyRegistrationResolver, new OpenSamlMetadataResolver()), Saml2WebSsoAuthenticationFilter.class);
    }
    @Bean
    public RequestMatcher saelRequestMatcher() {
        return new SaelRequestMatcher();
    }
    @Bean
    public LogoutSuccessHandler saelLogoutSuccessHanlder() {
        return new SaelLogoutSuccessHandler();
    }
    @Bean
    Saml2AuthenticationRequestFactory authenticationRequestFactory(
            AuthnRequestConverter authnRequestConverter) {

        OpenSamlAuthenticationRequestFactory authenticationRequestFactory =
                new OpenSamlAuthenticationRequestFactory();

        authenticationRequestFactory.setAuthenticationRequestContextConverter(authnRequestConverter);
        return authenticationRequestFactory;
    }

    @Bean
    public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception{
        KeyStore ks = KeyStore.getInstance(this.keyStoreType);
        char[] pwd = keyStorePassword != null ? keyStorePassword.toCharArray() : null;
        String ksName = keyStoreName.replaceAll("classpath:", "");
        Resource keystoreRes = new ClassPathResource(ksName);
        ks.load(keystoreRes.getInputStream(), pwd);
        PrivateKey privateKey = (PrivateKey)ks.getKey(keyStoreAlias, keyStoreKeyPassword.toCharArray());
        X509Certificate cert = (X509Certificate) ks.getCertificate(keyStoreAlias);

        RelyingPartyRegistration registration = RelyingPartyRegistrations
                .fromMetadataLocation(assertingPartyMetadataLocation)
                .registrationId(registrationId)
                .entityId(spEntityId)
                .signingX509Credentials((c) -> c.add(Saml2X509Credential.signing(privateKey, cert)))
                .decryptionX509Credentials((c)->c.add(Saml2X509Credential.decryption(privateKey, cert)))
                .build();
        return new InMemoryRelyingPartyRegistrationRepository(registration);
    }


    private String getValue( XMLObject valueObj, String defaultValue ) {
        if( valueObj instanceof XSStringImpl ) {

            XSStringImpl stringImpl = (XSStringImpl)valueObj;
            return stringImpl.getValue();
        }
        return defaultValue;
    }
}

May you help me in understanding if I'm missing something (I think I'm missing something)

Comment From: eleftherias

I'm glad to hear you solved the issue. It feels like your second question is better suited to Stack Overflow. We prefer to use GitHub issues only for bugs and enhancements. Feel free to update this issue with a link to the re-posted question (so that other people can find it) or add a minimal sample that reproduces this issue if you feel this is a genuine bug.