Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.1k views
in Technique[技术] by (71.8m points)

Spring Security SAML2 issue signing SAMLRequest

I'm using spring boot 2.4.1 and spring security SAML2 support I successfully configured my Service Provider. I created a self-signed certificate and I'm trying to use an IDP that requires signed AuthnRequests.

This is my RelyingPartyRegistrationRepository configuration:

@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);
}

The application successfully starts but,, every time I makes a new request, I got an exception on the IDP side because no KeyInfo element is found in the AuthnRequest

By seeing my application logs, I found this log:

2021-01-06 12:20:35,650 23472 [XNIO-1 task-7] INFO o.o.x.s.support.SignatureSupport - No KeyInfoGenerator was supplied in parameters or resolveable for credential type org.opensaml.security.x509.X509Credential, No KeyInfo will be generated for Signature

I can't understand if I'm missing something in the configuration.

Please note that the same happens also with a certificate released by a trusted CA and not only with self-signed certificate. So I'm thinking it's a kind of configuration mistake I'm doing or a kind of bug.

May you kindly give me tip in how to solve this issue?

Angelo

UPDATE

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("${applicazione.mock.external.idp.metadata.location}")
    private String assertingPartyMetadataLocation;
    @Value("${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)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)
等待大神答复

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...