Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
43832a8
Use plaform OCSP implementation by default, move custom OCSP implemen…
mrts Dec 12, 2025
b74273d
Add resilient OCSP certificate revocation checker
aarmam Jan 30, 2026
f588f58
AUT-2737 Change circuit breaker configuration parameter overrides
madislm Apr 21, 2026
5efaf4a
AUT-2679 Validate Basic Constraints and Key Usage extensions of OCSP …
aarmam Apr 14, 2026
2234f38
AUT-2730 Fix validation order
aarmam Apr 20, 2026
79f6708
AUT-2730 Throw UserCertificateOCSPCheckFailedException on service res…
aarmam Apr 20, 2026
bea4b65
AUT-2730 Compute the CertificateID once outside the request method
aarmam Apr 20, 2026
e0cb758
AUT-2730 Throw UserCertificateOCSPCheckFailedException on OCSP reques…
aarmam Apr 20, 2026
a6131ba
AUT-2730 Add UserCertificateOCSPException for pre OCSP request failures
aarmam Apr 21, 2026
5dfeba0
AUT-2730 Refactor OcspCertificateRevocationChecker to use UserCertifi…
aarmam Apr 21, 2026
9d31c0d
AUT-2730 Update note
aarmam Apr 21, 2026
195f144
AUT-2730 Remove redundant access location null check
aarmam Apr 28, 2026
b520b6a
AUT-2730 Extract getOcspRequest
aarmam Apr 27, 2026
323d982
AUT-2730 Mock date in validation order test to keep certificates valid
aarmam Aug 11, 2026
531fb48
AUT-2759 Improve test coverage
aarmam May 22, 2026
8a4abec
AUT-2802 Make maxThisUpdateAge and maxNextUpdateAge separately config…
madislm May 14, 2026
a979fa7
AUT-2981 Wrap unchecked JJWT exceptions in signature validation
aarmam Aug 13, 2026
34878be
AUT-2981 Capture circuit breaker statistics when the request starts
aarmam Aug 13, 2026
1bf54e0
AUT-2981 Fix compilation and tests after cherry-picking AUT-2981 commits
madislm Sep 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/coverity-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 11
java-version: 17

- name: Cache Maven packages
uses: actions/cache@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/maven-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 11
java-version: 17

- name: Cache Maven packages
uses: actions/cache@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/maven-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 11
java-version: 17

- name: Cache Maven packages
uses: actions/cache@v4
Expand Down
103 changes: 50 additions & 53 deletions README.md

Large diffs are not rendered by default.

14 changes: 2 additions & 12 deletions example/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
</parent>
<groupId>eu.webeid.example</groupId>
<artifactId>web-eid-springboot-example</artifactId>
<version>3.2.2</version>
<version>4.0.0-SNAPSHOT</version>
<name>web-eid-springboot-example</name>
<description>Example Spring Boot application that demonstrates how to use Web eID for authentication and digital
signing
Expand All @@ -21,9 +21,8 @@
<properties>
<java.version>17</java.version>
<maven-surefire-plugin.version>3.5.3</maven-surefire-plugin.version>
<webeid.version>3.2.1</webeid.version>
<webeid.version>4.0.0-SNAPSHOT</webeid.version>
<digidoc4j.version>6.1.1</digidoc4j.version>
<jmockit.version>1.44</jmockit.version> <!-- Keep version 1.44, otherwise mocking will fail. -->
<jib.version>3.5.1</jib.version>

<!-- Remove in next spring-boot version when tomcat is >= 10.1.55: CVE-2026-4193, CVE-2026-4512, CVE-2026-4315 -->
Expand Down Expand Up @@ -74,12 +73,6 @@
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>${jmockit.version}</version>
<scope>test</scope>
</dependency>

</dependencies>

Expand All @@ -93,9 +86,6 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<argLine>
-javaagent:${settings.localRepository}/org/jmockit/jmockit/${jmockit.version}/jmockit-${jmockit.version}.jar
</argLine>
<disableXmlReport>true</disableXmlReport>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ public AuthTokenValidator validator(YAMLConfig yamlConfig) {
.withSiteOrigin(URI.create(yamlConfig.getLocalOrigin()))
.withTrustedCertificateAuthorities(loadTrustedCACertificatesFromCerFiles())
.withTrustedCertificateAuthorities(loadTrustedCACertificatesFromTrustStore(yamlConfig))
.withOcspRequestTimeout(yamlConfig.getOcspRequestTimeout())
.build();
} catch (JceException e) {
throw new RuntimeException("Error building the Web eID auth token validator.", e);
Expand All @@ -77,7 +76,7 @@ public YAMLConfig yamlConfig() {
return new YAMLConfig();
}

private X509Certificate[] loadTrustedCACertificatesFromCerFiles() {
X509Certificate[] loadTrustedCACertificatesFromCerFiles() {
List<X509Certificate> caCertificates = new ArrayList<>();

try {
Expand All @@ -87,18 +86,19 @@ private X509Certificate[] loadTrustedCACertificatesFromCerFiles() {
Resource[] resources = resolver.getResources(CERTS_RESOURCE_PATH + activeProfile + "/*.cer");

for (Resource resource : resources) {
X509Certificate caCertificate = (X509Certificate) certFactory.generateCertificate(resource.getInputStream());
caCertificates.add(caCertificate);
try (InputStream stream = resource.getInputStream()) {
caCertificates.add((X509Certificate) certFactory.generateCertificate(stream));
}
}

} catch (CertificateException | IOException e) {
throw new RuntimeException("Error initializing trusted CA certificates.", e);
}

return caCertificates.toArray(new X509Certificate[0]);
return caCertificates.toArray(X509Certificate[]::new);
}

private X509Certificate[] loadTrustedCACertificatesFromTrustStore(YAMLConfig yamlConfig) {
X509Certificate[] loadTrustedCACertificatesFromTrustStore(YAMLConfig yamlConfig) {
List<X509Certificate> caCertificates = new ArrayList<>();

try (InputStream is = ValidationConfiguration.class.getResourceAsStream(CERTS_RESOURCE_PATH + activeProfile + "/" + TRUSTED_CERTIFICATES_JKS)) {
Expand All @@ -118,7 +118,7 @@ private X509Certificate[] loadTrustedCACertificatesFromTrustStore(YAMLConfig yam
throw new RuntimeException("Error initializing trusted CA certificates from trust store.", e);
}

return caCertificates.toArray(new X509Certificate[0]);
return caCertificates.toArray(X509Certificate[]::new);
}


Expand Down
11 changes: 0 additions & 11 deletions example/src/main/java/eu/webeid/example/config/YAMLConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

package eu.webeid.example.config;

import java.time.Duration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
Expand All @@ -23,8 +22,6 @@ public class YAMLConfig {
@Value("truststore-password")
private String trustStorePassword;

private Duration ocspRequestTimeout = Duration.ofSeconds(5L);

@Value("#{new Boolean('${web-eid-auth-token.validation.use-digidoc4j-prod-configuration}'.trim())}")
private Boolean useDigiDoc4jProdConfiguration;

Expand Down Expand Up @@ -59,12 +56,4 @@ public boolean getUseDigiDoc4jProdConfiguration() {
public void setUseDigiDoc4jProdConfiguration(boolean useDigiDoc4jProdConfiguration) {
this.useDigiDoc4jProdConfiguration = useDigiDoc4jProdConfiguration;
}

public Duration getOcspRequestTimeout() {
return ocspRequestTimeout;
}

public void setOcspRequestTimeout(Duration ocspRequestTimeout) {
this.ocspRequestTimeout = ocspRequestTimeout;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import eu.webeid.security.challenge.ChallengeNonceStore;
import eu.webeid.security.exceptions.AuthTokenException;
import eu.webeid.security.validator.AuthTokenValidator;
import eu.webeid.security.validator.ValidationInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationProvider;
Expand All @@ -20,12 +21,10 @@
import org.springframework.stereotype.Component;

import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.List;

/**
* Parses JWT from token string inside AuthTokenDTO and attempts authentication.
* Validates the Web eID authentication token supplied in AuthTokenDTO.
*/
@Component
public class AuthTokenDTOAuthenticationProvider implements AuthenticationProvider {
Expand All @@ -46,15 +45,17 @@ public AuthTokenDTOAuthenticationProvider(AuthTokenValidator tokenValidator, Cha
public Authentication authenticate(Authentication auth) throws AuthenticationException {
LOG.info("authenticate(): {}", auth);

final PreAuthenticatedAuthenticationToken authentication = (PreAuthenticatedAuthenticationToken) auth;
final WebEidAuthToken authToken = ((AuthTokenDTO) authentication.getCredentials()).getToken();
if (!(auth.getCredentials() instanceof AuthTokenDTO credentials) || credentials.token() == null) {
throw new AuthenticationServiceException("Authentication token is missing");
}
final WebEidAuthToken authToken = credentials.token();

final List<GrantedAuthority> authorities = Collections.singletonList(USER_ROLE);
final List<GrantedAuthority> authorities = List.of(USER_ROLE);

try {
final String nonce = challengeNonceStore.getAndRemove().getBase64EncodedNonce();
final X509Certificate userCertificate = tokenValidator.validate(authToken, nonce);
return WebEidAuthentication.fromCertificate(userCertificate, authorities);
final ValidationInfo validationInfo = tokenValidator.validate(authToken, nonce);
return WebEidAuthentication.fromCertificate(validationInfo.subjectCertificate(), authorities);
} catch (AuthTokenException e) {
throw new AuthenticationServiceException("Web eID token validation failed", e);
} catch (CertificateEncodingException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

package eu.webeid.example.security;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import eu.webeid.example.security.ajax.AjaxAuthenticationFailureHandler;
Expand Down Expand Up @@ -30,7 +31,7 @@

public class WebEidAjaxLoginProcessingFilter extends AbstractAuthenticationProcessingFilter {
private static final Logger LOG = LoggerFactory.getLogger(WebEidAjaxLoginProcessingFilter.class);
private final ObjectReader OBJECT_READER = new ObjectMapper().readerFor(AuthTokenDTO.class);
private static final ObjectReader OBJECT_READER = new ObjectMapper().readerFor(AuthTokenDTO.class);
private final SecurityContextRepository securityContextRepository;

public WebEidAjaxLoginProcessingFilter(
Expand Down Expand Up @@ -59,7 +60,15 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ
}

LOG.info("attemptAuthentication(): Reading request body");
final AuthTokenDTO authTokenDTO = OBJECT_READER.readValue(request.getReader());
final AuthTokenDTO authTokenDTO;
try {
authTokenDTO = OBJECT_READER.readValue(request.getReader());
} catch (JsonProcessingException e) {
throw new AuthenticationServiceException("Invalid authentication request", e);
}
if (authTokenDTO == null || authTokenDTO.token() == null) {
throw new AuthenticationServiceException("Authentication token is missing");
}
LOG.info("attemptAuthentication(): Creating token");
final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(null, authTokenDTO);
LOG.info("attemptAuthentication(): Calling authentication manager");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ private static String getPrincipalNameFromCertificate(X509Certificate userCertif

@Override
public boolean equals(Object o) {
if (!super.equals(o)) return false;
WebEidAuthentication that = (WebEidAuthentication) o;
return Objects.equals(idCode, that.idCode);
return o instanceof WebEidAuthentication that
&& super.equals(that)
&& Objects.equals(idCode, that.idCode);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@ public class AjaxAuthenticationFailureHandler extends SimpleUrlAuthenticationFai
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException {
final String message = AUTHENTICATION_FAILED + exception.getMessage();
LOG.warn("onAuthenticationFailure(): exception {}, returning {} {}",
exception,
HttpServletResponse.SC_UNAUTHORIZED,
message);
LOG.warn("Authentication failed; returning HTTP 401", exception);
final HttpSession session = request.getSession(false);
if (session != null) {
LOG.info("Invalidating session");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

package eu.webeid.example.security.ajax;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
Expand Down Expand Up @@ -42,19 +41,11 @@ public void onAuthenticationSuccess(
response.getWriter().write(AuthSuccessDTO.asJson(authentication));
}

public static class AuthSuccessDTO {
public record AuthSuccessDTO(String sub, String auth) {
private static final ObjectWriter OBJECT_WRITER = new ObjectMapper().writerFor(AuthSuccessDTO.class);

@JsonProperty("sub")
private String sub;

@JsonProperty("auth")
private String auth;

public static String asJson(Authentication authentication) throws JsonProcessingException {
final AuthSuccessDTO dto = new AuthSuccessDTO();
dto.sub = authentication.getName();
dto.auth = authentication.getAuthorities().toString();
final AuthSuccessDTO dto = new AuthSuccessDTO(authentication.getName(), authentication.getAuthorities().toString());
return OBJECT_WRITER.writeValueAsString(dto);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,5 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import eu.webeid.security.authtoken.WebEidAuthToken;

public class AuthTokenDTO {
@JsonProperty("auth-token")
private WebEidAuthToken token;

public WebEidAuthToken getToken() {
return token;
}

public void setToken(WebEidAuthToken token) {
this.token = token;
}
public record AuthTokenDTO(@JsonProperty("auth-token") WebEidAuthToken token) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,7 @@ public DigestDTO prepareContainer(CertificateDTO certificateDTO, WebEidAuthentic
final byte[] digest = signatureDigestAlgorithm.getDssDigestAlgorithm().getMessageDigest()
.digest(dataToSign.getDataToSign());

final DigestDTO digestDTO = new DigestDTO();
digestDTO.setHash(DatatypeConverter.printBase64Binary(digest));
digestDTO.setHashFunction(digestAlgorithmName);

return digestDTO;
return new DigestDTO(DatatypeConverter.printBase64Binary(digest), digestAlgorithmName);
}

/**
Expand All @@ -127,7 +123,7 @@ public FileDTO signContainer(SignatureDTO signatureDTO) {
Container containerToSign = (Container) Objects.requireNonNull(currentSession().getAttribute(SESSION_ATTR_CONTAINER));
DataToSign dataToSign = (DataToSign) Objects.requireNonNull(currentSession().getAttribute(SESSION_ATTR_DATA));

byte[] signatureBytes = DatatypeConverter.parseBase64Binary(signatureDTO.getBase64Signature());
byte[] signatureBytes = DatatypeConverter.parseBase64Binary(signatureDTO.base64Signature());
Signature signature = dataToSign.finalize(signatureBytes);
containerToSign.addSignature(signature);
currentSession().setAttribute(SESSION_ATTR_CONTAINER, containerToSign);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@

package eu.webeid.example.service.dto;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.stream.Collectors;

public class CertificateDTO {

Expand Down Expand Up @@ -41,11 +41,12 @@ public X509Certificate toX509Certificate() throws CertificateException {
return (X509Certificate) cf.generateCertificate(inStream);
}

@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public List<String> getSupportedHashFunctionNames() {
return supportedSignatureAlgorithms == null ? new ArrayList<>() : supportedSignatureAlgorithms
return supportedSignatureAlgorithms == null ? List.of() : supportedSignatureAlgorithms
.stream()
.map(SignatureAlgorithmDTO::getHashFunction)
.distinct()
.collect(Collectors.toList());
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,5 @@

package eu.webeid.example.service.dto;

public class ChallengeDTO {
private String nonce;

public String getNonce() {
return nonce;
}

public void setNonce(String nonce) {
this.nonce = nonce;
}
public record ChallengeDTO(String nonce) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,5 @@

package eu.webeid.example.service.dto;

public class DigestDTO {
private String hash;
private String hashFunction;

public String getHash() {
return hash;
}

public void setHash(String hash) {
this.hash = hash;
}

public String getHashFunction() {
return hashFunction;
}

public void setHashFunction(String hashFunction) {
this.hashFunction = hashFunction;
}
public record DigestDTO(String hash, String hashFunction) {
}
Loading