CVE-2021-44878 (Java, published 2022-01-06T13:15Z)
Description: value: If an OpenID Connect provider supports the "none" algorithm (i.e., tokens with no signature), pac4j v5.3.0 (and prior) does not refuse it without an explicit configuration on its side or for the "idtoken" response type which is not secure and violates the OpenID Core Specification. The "none" algorithm does not require any signature verification when validating the ID tokens, which allows the attacker to bypass the token validation by injecting a malformed ID token using "none" as the value of "alg" key in the header with an empty signature value.
Severity: MEDIUM (CVSS v3 base score: 7.5)
Weakness: CWE-347 \u2014 Improper Verification of Cryptographic Signature
The product does not verify, or incorrectly verifies, the cryptographic signature for data.
Repository: https://github.com/pac4j/pac4j
Files changed:
documentation/docs/clients/openid-connect.md
documentation/docs/release-notes.md
pac4j-oidc/src/main/java/org/pac4j/oidc/config/OidcConfiguration.java
pac4j-oidc/src/main/java/org/pac4j/oidc/credentials/authenticator/UserInfoOidcAuthenticator.java
pac4j-oidc/src/main/java/org/pac4j/oidc/profile/creator/TokenValidator.java
pac4j-oidc/src/test/java/org/pac4j/oidc/profile/creator/TokenValidatorTests.java
Commit message: reinforce security on OIDC


Vulnerable code:
            "tokenValidator", tokenValidator, "mappedClaims", mappedClaims);
import javax.naming.AuthenticationException;

                logger.error("Bad User Info response, error={}",
                    ((UserInfoErrorResponse) userInfoResponse).getErrorObject().toJSONObject());
                throw new AuthenticationException();
        } catch (IOException | ParseException | java.text.ParseException | AuthenticationException e) {
            if ("none".equals(jwsAlgorithm.getName())) {
                jwsAlgorithm = null;
            }

            if (jwsAlgorithm == null) {

Fixed code:

Since version 5.2 and to reinforce security, the `none` alogithm for ID tokens (meaning no signature validation) must be explictly accepted by using:

```java
config.setAllowUnsignedIdTokens(true);
```
- Reinforce security on the OIDC protocol support: the `none` algorithm must be explicitly accepted on client side (`allowUnsignedIdTokens`)
    private boolean allowUnsignedIdTokens;

    public boolean isAllowUnsignedIdTokens() {
        return allowUnsignedIdTokens;
    }

    public void setAllowUnsignedIdTokens(final boolean allowUnsignedIdTokens) {
        this.allowUnsignedIdTokens = allowUnsignedIdTokens;
    }

            "tokenValidator", tokenValidator, "mappedClaims", mappedClaims, "allowUnsignedIdTokens", allowUnsignedIdTokens);
                throw new TechnicalException("Bad User Info response, error="
                    + ((UserInfoErrorResponse) userInfoResponse).getErrorObject().toJSONObject());
        } catch (IOException | ParseException | java.text.ParseException e) {
            if ("none".equals(jwsAlgorithm.getName())) {
                if (!configuration.isAllowUnsignedIdTokens()) {
                    throw new TechnicalException("Unsigned ID tokens are not allowed");
                }
                logger.warn("Allowing unsigned ID tokens");

    // for tests
    List<IDTokenValidator> getIdTokenValidators() {
        return idTokenValidators;
    }
package org.pac4j.oidc.profile.creator;

import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.oauth2.sdk.id.Issuer;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import com.nimbusds.openid.connect.sdk.validators.IDTokenValidator;
import org.junit.Before;
import org.junit.Test;
import org.pac4j.core.exception.TechnicalException;
import org.pac4j.core.util.TestsConstants;
import org.pac4j.core.util.TestsHelper;
import org.pac4j.oidc.config.OidcConfiguration;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

/**
 * Tests {@link TokenValidator}.
 *
 * @author Jerome LELEU
 * @since 5.2.0
 */
public final class TokenValidatorTests implements TestsConstants {

    private OidcConfiguration configuration;

    private List<JWSAlgorithm> algorithms;

    @Before
    public void setUp() throws URISyntaxException {
        configuration = mock(OidcConfiguration.class);
        final OIDCProviderMetadata metadata = mock(OIDCProviderMetadata.class);
        when(metadata.getIssuer()).thenReturn(new Issuer(PAC4J_URL));
        when(metadata.getJWKSetURI()).thenReturn(new URI(PAC4J_BASE_URL));
        when(configuration.findProviderMetadata()).thenReturn(metadata);
        when(configuration.getClientId()).thenReturn(ID);
        when(configuration.getSecret()).thenReturn(SECRET);
        algorithms = new ArrayList<>();
        when(metadata.getIDTokenJWSAlgs()).thenReturn(algorithms);
    }

    @Test
    public void testNoAlgoDefinedAtProvider() {
        TestsHelper.expectException(() -> new TokenValidator(configuration), TechnicalException.class,
            "There must at least one JWS algorithm supported on the OpenID Connect provider side");
    }

    @Test
    public void testNoneAlgoNotAllowed() {
        algorithms.add(JWSAlgorithm.parse("none"));
        TestsHelper.expectException(() -> new TokenValidator(configuration), TechnicalException.class,
            "Unsigned ID tokens are not allowed");
    }

    @Test
    public void testNoneAlgoAllowed() {
        algorithms.add(JWSAlgorithm.parse("none"));
        when(configuration.isAllowUnsignedIdTokens()).thenReturn(true);
        final TokenValidator validator = new TokenValidator(configuration);
        final List<IDTokenValidator> validators = validator.getIdTokenValidators();
        assertEquals(1, validators.size());
        assertTrue(validators.get(0) instanceof IDTokenValidator);
    }

    @Test
    public void testTwoAlgorithms() {
        algorithms.add(JWSAlgorithm.HS256);
        algorithms.add(JWSAlgorithm.RS256);
        final TokenValidator validator = new TokenValidator(configuration);
        final List<IDTokenValidator> validators = validator.getIdTokenValidators();
        assertEquals(2, validators.size());
    }

    @Test
    public void testTwoAlgorithmsOnePreferred() {
        algorithms.add(JWSAlgorithm.HS256);
        algorithms.add(JWSAlgorithm.RS256);
        when(configuration.getPreferredJwsAlgorithm()).thenReturn(JWSAlgorithm.HS256);
        final TokenValidator validator = new TokenValidator(configuration);
        final List<IDTokenValidator> validators = validator.getIdTokenValidators();
        assertEquals(1, validators.size());
    }
}