From 15e849a62d6b3a62f8fde6c4bd9b5c9518401358 Mon Sep 17 00:00:00 2001 From: Sander Kondratjev Date: Mon, 13 Oct 2025 11:33:18 +0300 Subject: [PATCH 1/3] NFC-82 NFC signing support for web-eid example Signed-off-by: Sander Kondratjev --- .github/workflows/maven-build-example.yml | 14 +- example/README.md | 15 +- example/pom.xml | 8 +- .../config/ApplicationConfiguration.java | 9 +- .../config/ValidationConfiguration.java | 27 +- .../config/WebEidAuthTokenProperties.java | 50 + ...ation.java => WebEidMobileProperties.java} | 20 +- .../eu/webeid/example/config/YAMLConfig.java | 89 -- .../security/WebEidAuthentication.java | 40 +- .../WebEidAuthenticationProvider.java | 15 +- .../security/WebEidMobileAuthInitFilter.java | 19 +- .../ui/WebEidLoginPageGeneratingFilter.java | 18 +- .../example/service/MobileSigningService.java | 149 +++ .../example/service/SigningService.java | 9 +- .../example/web/rest/SigningController.java | 36 +- .../src/main/resources/application-dev.yaml | 5 +- .../src/main/resources/application-prod.yaml | 5 +- .../src/main/resources/static/css/main.css | 22 + .../src/main/resources/static/js/errors.js | 7 +- .../src/main/resources/static/js/payload.js | 45 + .../src/main/resources/templates/index.html | 1052 +++++++++-------- .../resources/templates/webeid-callback.html | 80 ++ .../resources/templates/webeid-login.html | 101 +- .../welcome-with-file-upload-support.html | 60 +- .../src/main/resources/templates/welcome.html | 281 +++-- .../security/WebEidAuthenticationTest.java | 4 +- .../webeid/example/testutil/HttpHelper.java | 2 +- .../src/test/resources/application-dev.yaml | 5 +- pom.xml | 2 +- 29 files changed, 1312 insertions(+), 877 deletions(-) create mode 100644 example/src/main/java/eu/webeid/example/config/WebEidAuthTokenProperties.java rename example/src/main/java/eu/webeid/example/config/{ThymeleafWebAppConfiguration.java => WebEidMobileProperties.java} (70%) delete mode 100644 example/src/main/java/eu/webeid/example/config/YAMLConfig.java create mode 100644 example/src/main/java/eu/webeid/example/service/MobileSigningService.java create mode 100644 example/src/main/resources/static/js/payload.js create mode 100644 example/src/main/resources/templates/webeid-callback.html diff --git a/.github/workflows/maven-build-example.yml b/.github/workflows/maven-build-example.yml index 3893cbd4..8f044b2a 100644 --- a/.github/workflows/maven-build-example.yml +++ b/.github/workflows/maven-build-example.yml @@ -10,10 +10,6 @@ on: - 'example/**' - '.github/workflows/*example*' -defaults: - run: - working-directory: ./example - jobs: build: runs-on: ubuntu-latest @@ -33,9 +29,9 @@ jobs: key: ${{ runner.os }}-m2-v17-${{ secrets.CACHE_VERSION }}-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-m2-v17-${{ secrets.CACHE_VERSION }} - - name: Build - run: mvn --batch-mode compile - - - name: Test and package - run: mvn --batch-mode package + - name: Install library + run: mvn -B -ntp install + - name: Build example project + working-directory: ./example + run: mvn -B -ntp package diff --git a/example/README.md b/example/README.md index 81938ac8..ed0c2cbb 100644 --- a/example/README.md +++ b/example/README.md @@ -82,6 +82,7 @@ When the application has started, open the _ngrok_ HTTPS URL in your preferred w - [Using DigiDoc4j in production mode with the `prod` profile](#using-digidoc4j-in-production-mode-with-the-prod-profile) + [Stateful and stateless authentication](#stateful-and-stateless-authentication) + [Assuring that the signing and authentication certificate subjects match](#assuring-that-the-signing-and-authentication-certificate-subjects-match) + + [Requesting the signing certificate in a separate step](#requesting-the-signing-certificate-in-a-separate-step) * [HTTPS support](#https-support) + [How to verify that HTTPS is configured properly](#how-to-verify-that-https-is-configured-properly) * [Deployment](#deployment) @@ -119,7 +120,9 @@ The `src/main/java/eu/webeid/example` directory contains the Spring Boot applica - `WebEidChallengeNonceFilter` for issuing the challenge nonce required by the authentication flow, - `WebEidMobileAuthInitFilter` for issuing the challenge nonce and generating the deep link with the authentication request, used to initiate the mobile authentication flow, - `WebEidAjaxLoginProcessingFilter` and `WebEidLoginPageGeneratingFilter` for handling login requests. -- `service`: Web eID signing service implementation that uses DigiDoc4j, and DigiDoc4j runtime configuration, +- `service`: Web eID signing service implementation that uses DigiDoc4j, and DigiDoc4j runtime configuration. + - `SigningService`: prepares ASiC-E containers and finalizes signatures. + - `MobileSigningService`: orchestrates the mobile signing flow (builds mobile signing requests/responses) and supports requesting the signing certificate in a separate step when enabled by configuration. - `web`: Spring Web MVC controller for the welcome page and Spring Web REST controller that provides a digital signing endpoint. The `src/resources` directory contains the resources used by the application: @@ -177,6 +180,16 @@ A common alternative to stateful authentication is stateless authentication with It is usually required to verify that the signing certificate subject matches the authentication certificate subject by assuring that both ID codes match. This check is implemented at the beginning of the `SigningService.prepareContainer()` method. +### Requesting the signing certificate in a separate step + +In some deployments, the signing certificate is not reused from the authentication flow. Instead, it is retrieved directly from the user’s ID-card during the signing process itself. + +This approach is useful when the signing process is performed without a prior authentication step. For example, in a mobile flow, the user may start signing directly without authenticating beforehand. In such cases, the signing certificate must be requested separately from the user’s ID-card before the signature can be created. + +When this mode is enabled in the configuration, the backend issues a separate request for the signing certificate using the `MobileSigningService`. The service communicates with the client to obtain the certificate before the signing container is prepared, ensuring that the correct certificate chain is available for the signature. + +This behavior is controlled by the `request-signing-cert` flag in the `application.yaml` configuration files (`application-dev.yaml`, `application-prod.yaml`). When the flag is set to **false**, the application explicitly requests the signing certificate during the signing process, demonstrating the separate signing certificate retrieval flow. When set to **true**, the signing uses the signing certificate that was already obtained during authentication, and no additional request is made. + ## HTTPS support There are two ways of adding HTTPS support to a Spring Boot application: diff --git a/example/pom.xml b/example/pom.xml index b50adcec..d9f5d6a8 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -10,7 +10,7 @@ eu.webeid.example web-eid-springboot-example - 3.2.0 + 4.0.0-SNAPSHOT web-eid-springboot-example Example Spring Boot application that demonstrates how to use Web eID for authentication and digital signing @@ -19,7 +19,7 @@ 17 3.5.3 - 3.2.0 + 4.0.0-SNAPSHOT 6.0.1 1.44 3.4.6 @@ -38,6 +38,10 @@ org.springframework.boot spring-boot-starter-thymeleaf + + org.springframework.boot + spring-boot-starter-validation + org.digidoc4j diff --git a/example/src/main/java/eu/webeid/example/config/ApplicationConfiguration.java b/example/src/main/java/eu/webeid/example/config/ApplicationConfiguration.java index c5a3a419..b46e5d08 100644 --- a/example/src/main/java/eu/webeid/example/config/ApplicationConfiguration.java +++ b/example/src/main/java/eu/webeid/example/config/ApplicationConfiguration.java @@ -28,6 +28,7 @@ import eu.webeid.example.security.WebEidMobileAuthInitFilter; import eu.webeid.example.security.ui.WebEidLoginPageGeneratingFilter; import eu.webeid.security.challenge.ChallengeNonceGenerator; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; @@ -39,9 +40,9 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler; import org.thymeleaf.ITemplateEngine; -import org.thymeleaf.web.servlet.JakartaServletWebApplication; @Configuration +@ConfigurationPropertiesScan @EnableWebSecurity @EnableMethodSecurity(securedEnabled = true) public class ApplicationConfiguration { @@ -53,7 +54,7 @@ public SecurityFilterChain filterChain( AuthenticationConfiguration authConfig, ChallengeNonceGenerator challengeNonceGenerator, ITemplateEngine templateEngine, - JakartaServletWebApplication webApp + WebEidMobileProperties webEidMobileProperties ) throws Exception { return http .authorizeHttpRequests(auth -> auth @@ -62,9 +63,9 @@ public SecurityFilterChain filterChain( .anyRequest().authenticated() ) .authenticationProvider(webEidAuthenticationProvider) - .addFilterBefore(new WebEidMobileAuthInitFilter("/auth/mobile/init", "/auth/mobile/login", challengeNonceGenerator), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(new WebEidMobileAuthInitFilter("/auth/mobile/init", "/auth/mobile/login", challengeNonceGenerator, webEidMobileProperties), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(new WebEidChallengeNonceFilter("/auth/challenge", challengeNonceGenerator), UsernamePasswordAuthenticationFilter.class) - .addFilterBefore(new WebEidLoginPageGeneratingFilter("/auth/mobile/login", "/auth/login", templateEngine, webApp), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(new WebEidLoginPageGeneratingFilter("/auth/mobile/login", "/auth/login", templateEngine), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(new WebEidAjaxLoginProcessingFilter("/auth/login", authConfig.getAuthenticationManager()), UsernamePasswordAuthenticationFilter.class) .logout(l -> l.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())) .headers(h -> h.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)) diff --git a/example/src/main/java/eu/webeid/example/config/ValidationConfiguration.java b/example/src/main/java/eu/webeid/example/config/ValidationConfiguration.java index 15818229..805c8b1b 100644 --- a/example/src/main/java/eu/webeid/example/config/ValidationConfiguration.java +++ b/example/src/main/java/eu/webeid/example/config/ValidationConfiguration.java @@ -79,24 +79,19 @@ public ChallengeNonceGenerator generator(ChallengeNonceStore challengeNonceStore } @Bean - public AuthTokenValidator validator(YAMLConfig yamlConfig) { + public AuthTokenValidator validator(WebEidAuthTokenProperties authTokenProperties) { try { return new AuthTokenValidatorBuilder() - .withSiteOrigin(URI.create(yamlConfig.getLocalOrigin())) + .withSiteOrigin(URI.create(authTokenProperties.validation().localOrigin())) .withTrustedCertificateAuthorities(loadTrustedCACertificatesFromCerFiles()) - .withTrustedCertificateAuthorities(loadTrustedCACertificatesFromTrustStore(yamlConfig)) - .withOcspRequestTimeout(yamlConfig.getOcspRequestTimeout()) + .withTrustedCertificateAuthorities(loadTrustedCACertificatesFromTrustStore(authTokenProperties)) + .withOcspRequestTimeout(authTokenProperties.validation().ocspRequestTimeout()) .build(); } catch (JceException e) { throw new RuntimeException("Error building the Web eID auth token validator.", e); } } - @Bean - public YAMLConfig yamlConfig() { - return new YAMLConfig(); - } - private X509Certificate[] loadTrustedCACertificatesFromCerFiles() { List caCertificates = new ArrayList<>(); @@ -117,8 +112,7 @@ private X509Certificate[] loadTrustedCACertificatesFromCerFiles() { return caCertificates.toArray(new X509Certificate[0]); } - - private X509Certificate[] loadTrustedCACertificatesFromTrustStore(YAMLConfig yamlConfig) { + private X509Certificate[] loadTrustedCACertificatesFromTrustStore(WebEidAuthTokenProperties authTokenProperties) { List caCertificates = new ArrayList<>(); try (InputStream is = ValidationConfiguration.class.getResourceAsStream(CERTS_RESOURCE_PATH + activeProfile + "/" + TRUSTED_CERTIFICATES_JKS)) { @@ -126,8 +120,14 @@ private X509Certificate[] loadTrustedCACertificatesFromTrustStore(YAMLConfig yam LOG.info("Truststore file {} not found for {} profile", TRUSTED_CERTIFICATES_JKS, activeProfile); return new X509Certificate[0]; } + + String trustStorePassword = authTokenProperties.validation().trustStorePassword(); + if (trustStorePassword == null || trustStorePassword.isBlank()) { + throw new IllegalStateException("Truststore password must be configured because truststore exists for profile '" + activeProfile + "'."); + } + KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); - keystore.load(is, yamlConfig.getTrustStorePassword().toCharArray()); + keystore.load(is, trustStorePassword.toCharArray()); Enumeration aliases = keystore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); @@ -140,7 +140,4 @@ private X509Certificate[] loadTrustedCACertificatesFromTrustStore(YAMLConfig yam return caCertificates.toArray(new X509Certificate[0]); } - - - } diff --git a/example/src/main/java/eu/webeid/example/config/WebEidAuthTokenProperties.java b/example/src/main/java/eu/webeid/example/config/WebEidAuthTokenProperties.java new file mode 100644 index 00000000..dee2db56 --- /dev/null +++ b/example/src/main/java/eu/webeid/example/config/WebEidAuthTokenProperties.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020-2025 Estonian Information System Authority + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package eu.webeid.example.config; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.bind.DefaultValue; +import org.springframework.validation.annotation.Validated; + +import java.time.Duration; + +@Validated +@ConfigurationProperties(prefix = "web-eid-auth-token") +public record WebEidAuthTokenProperties(WebEidAuthTokenValidation validation) { + + public record WebEidAuthTokenValidation( + @NotBlank + @Pattern( + regexp = "^https://[A-Za-z0-9.-]+(:\\d{1,5})?$", + message = "Origin URL must be in the form of 'https://' [ ':' ] and not end with a trailing slash" + ) + String localOrigin, + String siteCertHash, + String trustStorePassword, + @DefaultValue("5s") Duration ocspRequestTimeout, + @NotNull Boolean useDigiDoc4jProdConfiguration) { + } +} diff --git a/example/src/main/java/eu/webeid/example/config/ThymeleafWebAppConfiguration.java b/example/src/main/java/eu/webeid/example/config/WebEidMobileProperties.java similarity index 70% rename from example/src/main/java/eu/webeid/example/config/ThymeleafWebAppConfiguration.java rename to example/src/main/java/eu/webeid/example/config/WebEidMobileProperties.java index 30f53b96..cc509b22 100644 --- a/example/src/main/java/eu/webeid/example/config/ThymeleafWebAppConfiguration.java +++ b/example/src/main/java/eu/webeid/example/config/WebEidMobileProperties.java @@ -22,16 +22,14 @@ package eu.webeid.example.config; -import jakarta.servlet.ServletContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.thymeleaf.web.servlet.JakartaServletWebApplication; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; -@Configuration -public class ThymeleafWebAppConfiguration { - - @Bean - public JakartaServletWebApplication jakartaServletWebApplication(ServletContext servletContext) { - return JakartaServletWebApplication.buildApplication(servletContext); - } +@Validated +@ConfigurationProperties(prefix = "web-eid-mobile") +public record WebEidMobileProperties( + @NotBlank @Pattern(regexp = "^.*(?:[^/]|://)$", message = "Base URI must not have a trailing slash") String baseRequestUri, + boolean requestSigningCert) { } diff --git a/example/src/main/java/eu/webeid/example/config/YAMLConfig.java b/example/src/main/java/eu/webeid/example/config/YAMLConfig.java deleted file mode 100644 index 1c3359ae..00000000 --- a/example/src/main/java/eu/webeid/example/config/YAMLConfig.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2020-2025 Estonian Information System Authority - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -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; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableConfigurationProperties -@ConfigurationProperties(prefix = "web-eid-auth-token.validation") -public class YAMLConfig { - - @Value("local-origin") - private String localOrigin; - - @Value("site-cert-hash") - private String siteCertHash; - - @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; - - public String getLocalOrigin() { - return localOrigin; - } - - public void setLocalOrigin(String localOrigin) { - this.localOrigin = localOrigin; - } - - public String getSiteCertHash() { - return siteCertHash; - } - - public void setSiteCertHash(String siteCertHash) { - this.siteCertHash = siteCertHash; - } - - public String getTrustStorePassword() { - return trustStorePassword; - } - - public void setTrustStorePassword(String trustStorePassword) { - this.trustStorePassword = trustStorePassword; - } - - public boolean getUseDigiDoc4jProdConfiguration() { - return useDigiDoc4jProdConfiguration; - } - - public void setUseDigiDoc4jProdConfiguration(boolean useDigiDoc4jProdConfiguration) { - this.useDigiDoc4jProdConfiguration = useDigiDoc4jProdConfiguration; - } - - public Duration getOcspRequestTimeout() { - return ocspRequestTimeout; - } - - public void setOcspRequestTimeout(Duration ocspRequestTimeout) { - this.ocspRequestTimeout = ocspRequestTimeout; - } -} diff --git a/example/src/main/java/eu/webeid/example/security/WebEidAuthentication.java b/example/src/main/java/eu/webeid/example/security/WebEidAuthentication.java index 5ba3ebf7..5ab1a8b5 100644 --- a/example/src/main/java/eu/webeid/example/security/WebEidAuthentication.java +++ b/example/src/main/java/eu/webeid/example/security/WebEidAuthentication.java @@ -22,7 +22,9 @@ package eu.webeid.example.security; +import eu.webeid.security.authtoken.SupportedSignatureAlgorithm; import eu.webeid.security.certificate.CertificateData; +import org.springframework.lang.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; @@ -36,21 +38,21 @@ public class WebEidAuthentication extends PreAuthenticatedAuthenticationToken implements Authentication { private final String idCode; + private final transient String signingCertificate; + private final transient List supportedSignatureAlgorithms; - public static Authentication fromCertificate(X509Certificate userCertificate, List authorities) throws CertificateEncodingException { - final String principalName = getPrincipalNameFromCertificate(userCertificate); - final String idCode = CertificateData.getSubjectIdCode(userCertificate) - .orElseThrow(() -> new CertificateEncodingException("Certificate does not contain subject ID code")); - return new WebEidAuthentication(principalName, idCode, authorities); - } - - public String getIdCode() { - return idCode; - } - - private WebEidAuthentication(String principalName, String idCode, List authorities) { + private WebEidAuthentication(String principalName, String idCode, String signingCertificate, List supportedSignatureAlgorithms, List authorities) { super(principalName, idCode, authorities); this.idCode = idCode; + this.signingCertificate = signingCertificate; + this.supportedSignatureAlgorithms = supportedSignatureAlgorithms; + } + + public static Authentication fromCertificate(X509Certificate userCertificate, @Nullable String signingCertificate, @Nullable List supportedSignatureAlgorithms, List authorities) throws CertificateEncodingException { + final String principalName = getPrincipalNameFromCertificate(userCertificate); + final String idCode = CertificateData.getSubjectIdCode(userCertificate) + .orElseThrow(() -> new CertificateEncodingException("Certificate does not contain subject ID code")); + return new WebEidAuthentication(principalName, idCode, signingCertificate, supportedSignatureAlgorithms, authorities); } private static String getPrincipalNameFromCertificate(X509Certificate userCertificate) throws CertificateEncodingException { @@ -62,10 +64,22 @@ private static String getPrincipalNameFromCertificate(X509Certificate userCertif } else { // Organization certificates do not have given name and surname fields. return CertificateData.getSubjectCN(userCertificate) - .orElseThrow(() -> new CertificateEncodingException("Certificate does not contain subject CN")); + .orElseThrow(() -> new CertificateEncodingException("Certificate does not contain subject CN")); } } + public String getIdCode() { + return idCode; + } + + public String getSigningCertificate() { + return signingCertificate; + } + + public List getSupportedSignatureAlgorithms() { + return supportedSignatureAlgorithms; + } + @Override public boolean equals(Object o) { if (!super.equals(o)) return false; diff --git a/example/src/main/java/eu/webeid/example/security/WebEidAuthenticationProvider.java b/example/src/main/java/eu/webeid/example/security/WebEidAuthenticationProvider.java index ad845dff..032564fb 100644 --- a/example/src/main/java/eu/webeid/example/security/WebEidAuthenticationProvider.java +++ b/example/src/main/java/eu/webeid/example/security/WebEidAuthenticationProvider.java @@ -22,6 +22,8 @@ package eu.webeid.example.security; +import eu.webeid.example.config.WebEidMobileProperties; +import eu.webeid.security.authtoken.SupportedSignatureAlgorithm; import eu.webeid.security.authtoken.WebEidAuthToken; import eu.webeid.security.challenge.ChallengeNonceStore; import eu.webeid.security.exceptions.AuthTokenException; @@ -54,10 +56,12 @@ public class WebEidAuthenticationProvider implements AuthenticationProvider { private final AuthTokenValidator tokenValidator; private final ChallengeNonceStore challengeNonceStore; + private final boolean requireSigningCert; - public WebEidAuthenticationProvider(AuthTokenValidator tokenValidator, ChallengeNonceStore challengeNonceStore) { + public WebEidAuthenticationProvider(AuthTokenValidator tokenValidator, ChallengeNonceStore challengeNonceStore, WebEidMobileProperties webEidMobileProperties) { this.tokenValidator = tokenValidator; this.challengeNonceStore = challengeNonceStore; + this.requireSigningCert = webEidMobileProperties.requestSigningCert(); } @Override @@ -72,7 +76,14 @@ public Authentication authenticate(Authentication auth) throws AuthenticationExc try { final String nonce = challengeNonceStore.getAndRemove().getBase64EncodedNonce(); final X509Certificate userCertificate = tokenValidator.validate(authToken, nonce); - return WebEidAuthentication.fromCertificate(userCertificate, authorities); + final String signingCertificate = requireSigningCert + ? authToken.getUnverifiedSigningCertificate() + : null; + final List supportedSignatureAlgorithms = requireSigningCert + ? authToken.getSupportedSignatureAlgorithms() + : null; + + return WebEidAuthentication.fromCertificate(userCertificate, signingCertificate, supportedSignatureAlgorithms, authorities); } catch (AuthTokenException e) { throw new AuthenticationServiceException("Web eID token validation failed", e); } catch (CertificateEncodingException e) { diff --git a/example/src/main/java/eu/webeid/example/security/WebEidMobileAuthInitFilter.java b/example/src/main/java/eu/webeid/example/security/WebEidMobileAuthInitFilter.java index fe198010..a9b2638d 100644 --- a/example/src/main/java/eu/webeid/example/security/WebEidMobileAuthInitFilter.java +++ b/example/src/main/java/eu/webeid/example/security/WebEidMobileAuthInitFilter.java @@ -22,9 +22,13 @@ package eu.webeid.example.security; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import eu.webeid.example.config.WebEidMobileProperties; import eu.webeid.security.challenge.ChallengeNonceGenerator; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -47,11 +51,13 @@ public final class WebEidMobileAuthInitFilter extends OncePerRequestFilter { private final RequestMatcher requestMatcher; private final ChallengeNonceGenerator nonceGenerator; private final String mobileLoginPath; + private final WebEidMobileProperties webEidMobileProperties; - public WebEidMobileAuthInitFilter(String path, String mobileLoginPath, ChallengeNonceGenerator nonceGenerator) { + public WebEidMobileAuthInitFilter(String path, String mobileLoginPath, ChallengeNonceGenerator nonceGenerator, WebEidMobileProperties webEidMobileProperties) { this.requestMatcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, path); this.nonceGenerator = nonceGenerator; this.mobileLoginPath = mobileLoginPath; + this.webEidMobileProperties = webEidMobileProperties; } @Override @@ -69,7 +75,8 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, .path(mobileLoginPath).build().toUriString(); String payloadJson = OBJECT_WRITER.writeValueAsString( - new AuthPayload(challenge.getBase64EncodedNonce(), loginUri) + new AuthPayload(challenge.getBase64EncodedNonce(), loginUri, + webEidMobileProperties.requestSigningCert() ? Boolean.TRUE : null) ); String encoded = Base64.getEncoder().encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8)); String eidAuthUri = "web-eid-mobile://auth#" + encoded; @@ -78,9 +85,15 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, OBJECT_WRITER.writeValue(response.getWriter(), new AuthUri(eidAuthUri)); } - record AuthPayload(String challenge, @JsonProperty("login_uri") String loginUri) { + @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) + @JsonInclude(JsonInclude.Include.NON_NULL) + record AuthPayload( + String challenge, + String loginUri, + Boolean getSigningCertificate) { } + @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) record AuthUri(@JsonProperty("auth_uri") String authUri) { } } diff --git a/example/src/main/java/eu/webeid/example/security/ui/WebEidLoginPageGeneratingFilter.java b/example/src/main/java/eu/webeid/example/security/ui/WebEidLoginPageGeneratingFilter.java index 48818340..3a7d0f54 100644 --- a/example/src/main/java/eu/webeid/example/security/ui/WebEidLoginPageGeneratingFilter.java +++ b/example/src/main/java/eu/webeid/example/security/ui/WebEidLoginPageGeneratingFilter.java @@ -33,11 +33,8 @@ import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.web.filter.OncePerRequestFilter; -import org.springframework.web.servlet.support.RequestContextUtils; import org.thymeleaf.ITemplateEngine; -import org.thymeleaf.context.WebContext; -import org.thymeleaf.web.IWebExchange; -import org.thymeleaf.web.servlet.JakartaServletWebApplication; +import org.thymeleaf.context.Context; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -47,18 +44,15 @@ public final class WebEidLoginPageGeneratingFilter extends OncePerRequestFilter private final RequestMatcher requestMatcher; private final String loginProcessingPath; private final ITemplateEngine templateEngine; - private final JakartaServletWebApplication webApp; public WebEidLoginPageGeneratingFilter( String path, String loginProcessingPath, - ITemplateEngine templateEngine, - JakartaServletWebApplication webApp + ITemplateEngine templateEngine ) { this.requestMatcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, path); this.loginProcessingPath = loginProcessingPath; this.templateEngine = templateEngine; - this.webApp = webApp; } @Override @@ -74,16 +68,14 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull Ht csrf = (CsrfToken) request.getAttribute("_csrf"); } - String html = renderTemplate(request, response, csrf); + String html = renderTemplate(csrf); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setContentType(MediaType.TEXT_HTML_VALUE); response.getWriter().write(html); } - private String renderTemplate(HttpServletRequest request, HttpServletResponse response, CsrfToken csrf) { - IWebExchange exchange = webApp.buildExchange(request, response); - var locale = RequestContextUtils.getLocale(request); - var ctx = new WebContext(exchange, locale); + private String renderTemplate(CsrfToken csrf) { + var ctx = new Context(); ctx.setVariable("loginProcessingPath", loginProcessingPath); ctx.setVariable("csrfHeaderName", csrf != null ? csrf.getHeaderName() : "X-CSRF-TOKEN"); ctx.setVariable("csrfToken", csrf != null ? csrf.getToken() : ""); diff --git a/example/src/main/java/eu/webeid/example/service/MobileSigningService.java b/example/src/main/java/eu/webeid/example/service/MobileSigningService.java new file mode 100644 index 00000000..161c3835 --- /dev/null +++ b/example/src/main/java/eu/webeid/example/service/MobileSigningService.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2020-2025 Estonian Information System Authority + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package eu.webeid.example.service; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import eu.webeid.example.config.WebEidMobileProperties; +import eu.webeid.example.security.WebEidAuthentication; +import eu.webeid.example.service.dto.CertificateDTO; +import eu.webeid.example.service.dto.DigestDTO; +import eu.webeid.example.service.dto.SignatureAlgorithmDTO; +import eu.webeid.security.authtoken.SupportedSignatureAlgorithm; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import org.springframework.web.util.UriComponentsBuilder; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.Base64; +import java.util.List; +import java.util.Objects; + +@Component +public class MobileSigningService { + public static final String CERTIFICATE_RESPONSE_PATH = "/sign/mobile/certificate"; + public static final String SIGNATURE_RESPONSE_PATH = "/sign/mobile/signature"; + public static final String WEB_EID_MOBILE_SIGN_PATH = "sign"; + public static final String WEB_EID_GET_CERT_PATH = "cert"; + private static final Logger LOG = LoggerFactory.getLogger(MobileSigningService.class); + private static final ObjectWriter OBJECT_WRITER = new ObjectMapper().writerFor(RequestObject.class); + private final SigningService signingService; + private final WebEidMobileProperties webEidMobileProperties; + + public MobileSigningService(SigningService signingService, WebEidMobileProperties webEidMobileProperties) { + this.signingService = signingService; + this.webEidMobileProperties = webEidMobileProperties; + } + + public MobileInitRequest initCertificateOrSigningRequest(WebEidAuthentication authentication) throws IOException, CertificateException, NoSuchAlgorithmException { + String signingCertificate = authentication.getSigningCertificate(); + List supportedSignatureAlgorithms = authentication.getSupportedSignatureAlgorithms(); + if (signingCertificate == null || supportedSignatureAlgorithms == null) { + return initCertificateRequest(); + } + CertificateDTO certificateDTO = new CertificateDTO(); + certificateDTO.setCertificate(signingCertificate); + certificateDTO.setSupportedSignatureAlgorithms(mapSupportedAlgorithms(supportedSignatureAlgorithms)); + return initSigningRequest(authentication, certificateDTO); + } + + public MobileInitRequest initSigningRequest(WebEidAuthentication authentication, CertificateDTO certificateDTO) throws IOException, CertificateException, NoSuchAlgorithmException { + Objects.requireNonNull(authentication, "authentication must not be null"); + Objects.requireNonNull(certificateDTO, "certificateDTO must not be null"); + final String responseUri = ServletUriComponentsBuilder.fromCurrentContextPath() + .path(SIGNATURE_RESPONSE_PATH) + .build() + .toUriString(); + final DigestDTO digest = signingService.prepareContainer(certificateDTO, authentication); + final RequestObject initRequest = new RequestObject( + responseUri, + certificateDTO.getCertificate(), + digest.getHash(), + digest.getHashFunction()); + final String payloadJson = OBJECT_WRITER.writeValueAsString(initRequest); + final String encoded = Base64.getUrlEncoder() + .withoutPadding() + .encodeToString(payloadJson.getBytes()); + final String requestUri = getRequestUri(WEB_EID_MOBILE_SIGN_PATH, encoded); + + return new MobileInitRequest(requestUri); + } + + private MobileInitRequest initCertificateRequest() throws IOException { + final String responseUri = ServletUriComponentsBuilder.fromCurrentContextPath() + .path(CERTIFICATE_RESPONSE_PATH) + .build() + .toUriString(); + final RequestObject initRequest = new RequestObject(responseUri, null, null, null); + final String payloadJson = OBJECT_WRITER.writeValueAsString(initRequest); + final String encoded = Base64.getUrlEncoder() + .withoutPadding() + .encodeToString(payloadJson.getBytes()); + final String requestUri = getRequestUri(WEB_EID_GET_CERT_PATH, encoded); + + return new MobileInitRequest(requestUri); + } + + private String getRequestUri(String path, String encodedPayload) { + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(webEidMobileProperties.baseRequestUri()); + if (webEidMobileProperties.baseRequestUri().startsWith("http")) { + builder.pathSegment(path); + } else { + builder.host(path); + } + return builder.fragment(encodedPayload).toUriString(); + } + + private List mapSupportedAlgorithms(List algorithms) { + return algorithms.stream().map(ssa -> { + var dto = new SignatureAlgorithmDTO(); + dto.setCryptoAlgorithm(ssa.getCryptoAlgorithm()); + dto.setHashFunction(ssa.getHashFunction()); + dto.setPaddingScheme(ssa.getPaddingScheme()); + return dto; + }).toList(); + } + + @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) + public record MobileInitRequest( + String requestUri + ) { + } + + @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) + @JsonInclude(JsonInclude.Include.NON_NULL) + record RequestObject( + String responseUri, + String signingCertificate, + String hash, + String hashFunction + ) { + } +} diff --git a/example/src/main/java/eu/webeid/example/service/SigningService.java b/example/src/main/java/eu/webeid/example/service/SigningService.java index dfbbc4b9..b1202dea 100644 --- a/example/src/main/java/eu/webeid/example/service/SigningService.java +++ b/example/src/main/java/eu/webeid/example/service/SigningService.java @@ -22,7 +22,7 @@ package eu.webeid.example.service; -import eu.webeid.example.config.YAMLConfig; +import eu.webeid.example.config.WebEidAuthTokenProperties; import eu.webeid.example.security.WebEidAuthentication; import eu.webeid.example.service.dto.CertificateDTO; import eu.webeid.example.service.dto.DigestDTO; @@ -67,13 +67,12 @@ public class SigningService { private static final String SESSION_ATTR_DATA = "data-to-sign"; private static final Logger LOG = LoggerFactory.getLogger(SigningService.class); private final Configuration signingConfiguration; - private final ObjectFactory httpSessionFactory; - public SigningService(ObjectFactory httpSessionFactory, YAMLConfig yamlConfig) { + public SigningService(ObjectFactory httpSessionFactory, WebEidAuthTokenProperties authTokenProperties) { this.httpSessionFactory = httpSessionFactory; - signingConfiguration = Configuration.of(yamlConfig.getUseDigiDoc4jProdConfiguration() ? - Configuration.Mode.PROD : Configuration.Mode.TEST); + signingConfiguration = Configuration.of(authTokenProperties.validation().useDigiDoc4jProdConfiguration() ? + Configuration.Mode.PROD : Configuration.Mode.TEST); // Use automatic AIA OCSP URL selection from certificate for signatures. signingConfiguration.setPreferAiaOcsp(true); } diff --git a/example/src/main/java/eu/webeid/example/web/rest/SigningController.java b/example/src/main/java/eu/webeid/example/web/rest/SigningController.java index b6eccf17..2c36c46c 100644 --- a/example/src/main/java/eu/webeid/example/web/rest/SigningController.java +++ b/example/src/main/java/eu/webeid/example/web/rest/SigningController.java @@ -23,6 +23,8 @@ package eu.webeid.example.web.rest; import eu.webeid.example.security.WebEidAuthentication; +import eu.webeid.example.service.MobileSigningService; +import eu.webeid.example.service.MobileSigningService.MobileInitRequest; import eu.webeid.example.service.SigningService; import eu.webeid.example.service.dto.CertificateDTO; import eu.webeid.example.service.dto.DigestDTO; @@ -38,6 +40,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.ModelAndView; import java.io.IOException; import java.security.NoSuchAlgorithmException; @@ -51,9 +54,11 @@ public class SigningController { private final SigningService signingService; + private final MobileSigningService mobileSigningService; - public SigningController(SigningService signingService) { + public SigningController(SigningService signingService, MobileSigningService mobileSigningService) { this.signingService = signingService; + this.mobileSigningService = mobileSigningService; } @PostMapping("prepare") @@ -61,11 +66,36 @@ public DigestDTO prepare(@RequestBody CertificateDTO data, WebEidAuthentication return signingService.prepareContainer(data, authentication); } - @PostMapping("sign") - public FileDTO sign(@RequestBody SignatureDTO data) { + @PostMapping("signature") + public FileDTO signature(@RequestBody SignatureDTO data) { return signingService.signContainer(data); } + @PostMapping("mobile/init") + public ResponseEntity mobileInit(WebEidAuthentication authentication) throws CertificateException, IOException, NoSuchAlgorithmException { + return ResponseEntity.ok(mobileSigningService.initCertificateOrSigningRequest(authentication)); + } + + @GetMapping("mobile/certificate") + public ModelAndView mobileCertificateResponse() { + return new ModelAndView("webeid-callback"); + } + + @PostMapping("mobile/certificate") + public ResponseEntity mobileCertificate(@RequestBody CertificateDTO certificateDTO, WebEidAuthentication authentication) throws CertificateException, IOException, NoSuchAlgorithmException { + return ResponseEntity.ok(mobileSigningService.initSigningRequest(authentication, certificateDTO)); + } + + @GetMapping("mobile/signature") + public ModelAndView mobileSignatureResponse() { + return new ModelAndView("webeid-callback"); + } + + @PostMapping("mobile/signature") + public ResponseEntity mobileSignature(@RequestBody SignatureDTO signatureDTO) { + return ResponseEntity.ok(signingService.signContainer(signatureDTO)); + } + @GetMapping(value = "download", produces = "application/vnd.etsi.asic-e+zip") public ResponseEntity download() throws IOException { diff --git a/example/src/main/resources/application-dev.yaml b/example/src/main/resources/application-dev.yaml index 9c637326..75db6ba0 100644 --- a/example/src/main/resources/application-dev.yaml +++ b/example/src/main/resources/application-dev.yaml @@ -1,4 +1,7 @@ web-eid-auth-token: validation: - use-digidoc4j-prod-configuration: false + use-digi-doc4j-prod-configuration: false local-origin: "https://test.web-eid.eu" +web-eid-mobile: + base-request-uri: "web-eid-mobile://" + request-signing-cert: false diff --git a/example/src/main/resources/application-prod.yaml b/example/src/main/resources/application-prod.yaml index 3868f350..c457453a 100644 --- a/example/src/main/resources/application-prod.yaml +++ b/example/src/main/resources/application-prod.yaml @@ -1,8 +1,11 @@ web-eid-auth-token: validation: - use-digidoc4j-prod-configuration: true + use-digi-doc4j-prod-configuration: true local-origin: "https://web-eid.eu" truststore-password: "changeit" +web-eid-mobile: + base-request-uri: "web-eid-mobile://" + request-signing-cert: false spring: thymeleaf: cache: true diff --git a/example/src/main/resources/static/css/main.css b/example/src/main/resources/static/css/main.css index 6e7f2b07..12b79bfe 100644 --- a/example/src/main/resources/static/css/main.css +++ b/example/src/main/resources/static/css/main.css @@ -116,3 +116,25 @@ body { background-color: transparent !important; } +/* Mobile callback loading styles */ +.loading-page { + text-align: center; + padding-top: 3rem; + font-family: system-ui, sans-serif; +} + +.spinner { + width: 40px; + height: 40px; + border: 4px solid #ccc; + border-top-color: #007bff; + border-radius: 50%; + animation: spin 1s linear infinite; + margin: 1rem auto; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} diff --git a/example/src/main/resources/static/js/errors.js b/example/src/main/resources/static/js/errors.js index 7f42d5ff..80414751 100644 --- a/example/src/main/resources/static/js/errors.js +++ b/example/src/main/resources/static/js/errors.js @@ -32,10 +32,9 @@ export function hideErrorMessage() { alertUi.alert.style.display = "none"; } -export function showErrorMessage(error) { - const message = "Authentication failed"; +export function showErrorMessage(error, message = "Authentication failed") { const details = - `[Code]\n${error.code}` + + `[Code]\n${error.code ?? "UNKNOWN_ERROR"}` + `\n\n[Message]\n${error.message}` + (error.response ? `\n\n[response]\n${JSON.stringify(error.response, null, " ")}` : ""); @@ -56,4 +55,4 @@ export async function checkHttpError(response) { error.code = response.status; throw error; } -} \ No newline at end of file +} diff --git a/example/src/main/resources/static/js/payload.js b/example/src/main/resources/static/js/payload.js new file mode 100644 index 00000000..4035e6c2 --- /dev/null +++ b/example/src/main/resources/static/js/payload.js @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020-2025 Estonian Information System Authority + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +export function parsePayload(context) { + const fragment = window.location.hash.slice(1); + + if (!fragment) { + throw new Error(`Missing ${context} response payload`); + } + + let payload; + try { + payload = JSON.parse(atob(fragment)); + } catch (e) { + console.error(e); + throw new Error(`Failed to parse the ${context} response`); + } + + if (payload.error) { + const error = new Error(payload.message ?? `${context} failed`); + error.code = payload.code; + throw error; + } + + return payload; +} diff --git a/example/src/main/resources/templates/index.html b/example/src/main/resources/templates/index.html index 415e53f3..64dc882b 100644 --- a/example/src/main/resources/templates/index.html +++ b/example/src/main/resources/templates/index.html @@ -1,516 +1,624 @@ - + - - - - - - - Web eID: electronic ID smart cards on the Web - - - - - - - - - - -
-
-
-

Web eID: electronic ID smart cards on the Web

-

- The Web eID project enables usage of European Union electronic identity (eID) smart cards for - secure authentication and digital signing of documents on the web using public-key cryptography. -

-

- Estonian, Finnish, Latvian, Lithuanian, Belgian and Croatian eID cards are supported in the first phase, - but only Estonian eID card support is currently enabled in the test application below. -

-

- Please get in touch by email at help@ria.ee in case you need support with adding Web eID to your project - or want to add support for a new eID card to Web eID. -

-

The privacy policy of the test service is available here. -

- -
-
Web eID plugin status
-
- - Web eID plugin installed -
-
- - Accessed with a mobile device -
+ + + + + + + Web eID: electronic ID smart cards on the Web + + + + + + + + + + +
+
+
+

Web eID: electronic ID smart cards on the Web

+

+ The Web eID project enables usage of European Union electronic identity (eID) smart cards for + secure authentication and digital signing of documents on the web using public-key cryptography. +

+

+ Estonian, Finnish, Latvian, Lithuanian, Belgian and Croatian eID cards are supported in the + first phase, but only Estonian eID card support is currently enabled in the test application + below. +

+

+ Please get in touch by email at help@ria.ee in case you need support with adding Web eID to your + project or want to add support for a new eID card to Web eID. +

+

+ The privacy policy of the test service is available + here. +

+ +
+
Web eID plugin status
+
+ + Web eID plugin installed +
+
+ + Accessed with a mobile device +
-
-

Table of contents

- - -
-

Usage with Web eID plugin

-

The recommended way of installing Web eID is by installing - the latest Open-EID ID-software package. - In case you do not need or want to install the Open-EID package, install the latest Web eID packages in - Firefox, Chrome, Edge or Safari according to the following instructions: -

-
    -
  1. - Download and run the Web eID native app and browser extension installer: - -
  2. -
  3. - The installer will install the browser extension for all supported browsers automatically. - The extension must be manually enabled from either the extension installation pop-up that appears in - the browser or from the browser extensions management page and may need browser restart under - certain circumstances. -
  4. -
-

Testing:

-
    -
  1. - Attach a smart card reader to the computer and insert the eID card into the reader. -
  2. -
  3. Click Authenticate below.
  4. -
- - -

- -

- -
-
-
-

- - -

-
-
-

The uninstaller will remove the browser extension from all supported browsers - automatically.

- -
Ubuntu Linux
-

Uninstall the Web eID software either using the Ubuntu Software Center or from the - console with
- sudo apt purge web-eid

- -
macOS
-

To uninstall the Web eID software, do the following:

-
    -
  1. download the Web eID native app and browser extension installer as instructed - above, -
  2. -
  3. open the downloaded file,
  4. -
  5. open Terminal,
  6. -
  7. drag and drop uninstall.sh from the downloaded file to the - Terminal window, -
  8. -
  9. press Enter and Y,
  10. -
  11. enter your password.
  12. -
-
Windows
-

Uninstall the Web eID software using Add or remove programs.

-
-
-
-
-

- - -

-
-
+
+

Usage with Web eID plugin

+

+ The recommended way of installing Web eID is by installing + the latest Open-EID ID-software package. In case you do not need or want to install the Open-EID package, install the latest Web eID + packages in Firefox, Chrome, Edge or Safari according to the following instructions: +

+
    +
  1. + Download and run the Web eID native app and browser extension installer:
    • - To debug the extension, open the extension page and select - Inspect to open browser developer tools in extension mode. You can examine - extension - logs in the Console tab, put breakpoints in extension code in the - Debugger tab - and inspect extension network communication in the Network tab. + on Ubuntu Linux, for Firefox and Chrome, download and execute the
      + install-web-eid.sh + script from the console with
      + wget -O - https:///scripts/install-web-eid.sh | + bash
      + Note: as of the 2.5 version, Web eID supports Firefox installed via Snap.
    • - To enable logging in the extension companion native app, -
        -
      • in Linux, run the following command in the console:
        - echo 'logging=true' > ~/.config/RIA/web-eid.conf -
      • -
      • in macOS, run the following command in the console:
        - defaults write \
        -   "$HOME/Library/Containers/eu.web-eid.web-eid/Data/Library/Preferences/eu.web-eid.web-eid.plist" - \
        -   logging true
        - defaults write - "$HOME/Library/Containers/eu.web-eid.web-eid-safari/Data/Library/Preferences/eu.web-eid.web-eid-safari.plist" - \
        -   logging true
        -
      • -
      • in Windows, add the following registry key:
        - [HKEY_CURRENT_USER\SOFTWARE\RIA\web-eid]
        - "logging"="true" -
      • -
      + on macOS 13 or later, for Firefox and Chrome from + here,
    • - The native app logs are stored in -
        -
      • ~/.local/share/RIA/web-eid/web-eid.log in Linux
      • -
      • ~/Library/Containers/eu.web-eid.web-eid/Data/Library/Application\ - Support/RIA/web-eid/web-eid.log in macOS -
      • -
      • ~/Library/Containers/eu.web-eid.web-eid-safari/Data/Library/Application\ - Support/RIA/web-eid-safari/web-eid-safari.log of Safari in macOS -
      • -
      • - C:/Users/<USER>/AppData/Local/RIA/web-eid/web-eid.log in - Windows. -
      • -
      + on macOS 13 or later, for Safari, install the extension from + App Store,
    • - You can verify if debugging works by executing the native application - web-eid - manually, - there will be an informative message in the logs. + on Windows 10, Windows 11, Windows Server 2019, Windows Server 2022, Windows + Server 2025 for Firefox, Chrome and Edge from + here.
    -
+ +
  • + The installer will install the browser extension for all supported browsers automatically. + The extension must be manually enabled from either the extension installation pop-up that + appears in the browser or from the browser extensions management page and may need browser + restart under certain circumstances. +
  • + +

    Testing:

    +
      +
    1. Attach a smart card reader to the computer and insert the eID card into the reader.
    2. +
    3. Click Authenticate below.
    4. +
    + + -
    -
    -

    - -

    -
    -
    -

    - Technical overview of the solution is available in the project - system architecture - document. - Overview of authentication token validation implementation in the back end is available - in the - web-eid-authtoken-validation-java Java library - README. -

    -

    - Security analysis of the solution is available - in - this - document. -

    +

    + +

    + +
    +
    +
    +

    + + +

    +
    +
    +

    + The uninstaller will remove the browser extension from all supported browsers + automatically. +

    + +
    Ubuntu Linux
    +

    + Uninstall the Web eID software either using the Ubuntu Software Center or from + the console with
    + sudo apt purge web-eid +

    + +
    macOS
    +

    To uninstall the Web eID software, do the following:

    +
      +
    1. + download the Web eID native app and browser extension installer as + instructed above, +
    2. +
    3. open the downloaded file,
    4. +
    5. open Terminal,
    6. +
    7. + drag and drop uninstall.sh from the downloaded file to the + Terminal window, +
    8. +
    9. press Enter and Y,
    10. +
    11. enter your password.
    12. +
    + +
    Windows
    +

    Uninstall the Web eID software using Add or remove programs.

    +
    +
    -
    -
    -
    -

    - -

    -
    -
    -

    - Currently the Web eID back-end libraries are available for Java, .NET and PHP web - applications. -

    -

    - To implement authentication and digital signing with Web eID in a Java, .NET or PHP web - application, - you need to -

    -
      -
    • use the web-eid.js JavaScript library in the front end of the web application - according to the instructions - here, -
    • -
    • for authentication +
      +

      + + +

      +
      +
        -
      • in Java use the web-eid-authtoken-validation-java library in - the back end of the web application according to the instructions - here, +
      • + To debug the extension, open the extension page and select + Inspect to open browser developer tools in extension mode. You can + examine extension logs in the Console tab, put breakpoints in + extension code in the Debugger tab and inspect extension network + communication in the Network tab. +
      • +
      • + To enable logging in the extension companion native app, +
          +
        • + in Linux, run the following command in the console:
          + echo 'logging=true' > ~/.config/RIA/web-eid.conf +
        • +
        • + in macOS, run the following command in the console:
          + defaults write \
          +   "$HOME/Library/Containers/eu.web-eid.web-eid/Data/Library/Preferences/eu.web-eid.web-eid.plist" + \
          +   logging true
          + defaults write + "$HOME/Library/Containers/eu.web-eid.web-eid-safari/Data/Library/Preferences/eu.web-eid.web-eid-safari.plist" + \
          +   logging true
          +
        • +
        • + in Windows, add the following registry key:
          + [HKEY_CURRENT_USER\SOFTWARE\RIA\web-eid]
          + "logging"="true" +
        • +
      • -
      • in .NET/C# use the web-eid-authtoken-validation-dotnet library - according to the - instructions - here +
      • + The native app logs are stored in +
          +
        • ~/.local/share/RIA/web-eid/web-eid.log in Linux
        • +
        • + ~/Library/Containers/eu.web-eid.web-eid/Data/Library/Application\ + Support/RIA/web-eid/web-eid.log + in macOS +
        • +
        • + ~/Library/Containers/eu.web-eid.web-eid-safari/Data/Library/Application\ + Support/RIA/web-eid-safari/web-eid-safari.log + of Safari in macOS +
        • +
        • + C:/Users/<USER>/AppData/Local/RIA/web-eid/web-eid.log + in Windows. +
        • +
      • -
      • in PHP use the web-eid-authtoken-validation-php library according to - the - instructions - here +
      • + You can verify if debugging works by executing the native application + web-eid + manually, there will be an informative message in the logs.
      -
    • -
    • for digital signing +
    +
    +
    +
    +

    + +

    +
    +
    +

    + Technical overview of the solution is available in the project + system architecture document. Overview of authentication token validation implementation in the back end is + available in the web-eid-authtoken-validation-java Java library + README. +

    +

    + Security analysis of the solution is available + in this document. +

    +
    +
    +
    +
    +

    + +

    +
    +
    +

    + Currently the Web eID back-end libraries are available for Java, .NET and PHP + web applications. +

    +

    + To implement authentication and digital signing with Web eID in a Java, .NET or + PHP web application, you need to +

      -
    • in Java use the digidoc4j library in the back end of the web +
    • + use the web-eid.js JavaScript library in the front end of the web application according to the instructions - here, + here,
    • -
    • in .NET/C# use the libdigidocpp library in the back end of the web - application according to the instructions - here. +
    • + for authentication +
        +
      • + in Java use the web-eid-authtoken-validation-java library in + the back end of the web application according to the instructions + here, +
      • +
      • + in .NET/C# use the + web-eid-authtoken-validation-dotnet library according to the + instructions + here +
      • +
      • + in PHP use the web-eid-authtoken-validation-php library + according to the instructions + here +
      • +
      +
    • +
    • + for digital signing +
        +
      • + in Java use the digidoc4j library in the back end of the web + application according to the instructions + here, +
      • +
      • + in .NET/C# use the libdigidocpp library in the back end of + the web application according to the instructions + here. +
      • +
    - - -

    - The full source code of an example Spring Boot web application that uses Web eID for - authentication - and digital signing is available - here. - The .NET/C# version of the example is available - here. - The PHP version of the example is available - here. -

    +

    + The full source code of an example Spring Boot web application that uses Web eID + for authentication and digital signing is available + here. The .NET/C# version of the example is available + here. The + PHP version of the example is available + here. +

    +
    +
    -
    -
    - -
    -

    Usage without Web eID plugin

    -

    The Web eID solution can also be used without installing the Web eID native app and browser extension. - This includes devices like mobile phones, tablets, and some Chromebooks, where the Web eID plugin cannot - currently be installed. -

    -

    - -

    - -
    -
    -
    -

    - -

    -
    -
    -

    - Technical overview of the solution is available in the project - system - architecture document. - Overview of authentication token validation implementation in the back end is available - in the - web-eid-authtoken-validation-java Java library - README. -

    +
    +

    Usage without Web eID plugin

    +

    + The Web eID solution can also be used without installing the Web eID native app and browser + extension. This includes devices like mobile phones, tablets, and some Chromebooks, where the + Web eID plugin cannot currently be installed. +

    +

    + +

    + +
    +
    +
    +

    + +

    +
    +
    +

    + Technical overview of the solution is available in the project + system architecture document. Overview of authentication token validation implementation in the back end is + available in the web-eid-authtoken-validation-java Java library + README. +

    +
    +
    -
    -
    - -
    - EU fund flags -
    - - - + + - + //# sourceURL=index.js + + diff --git a/example/src/main/resources/templates/webeid-callback.html b/example/src/main/resources/templates/webeid-callback.html new file mode 100644 index 00000000..cbc6fc49 --- /dev/null +++ b/example/src/main/resources/templates/webeid-callback.html @@ -0,0 +1,80 @@ + + + + + + + + + Completing signing… + + + + + +
    +

    Completing signing…

    +
    +
    + + + + + + + diff --git a/example/src/main/resources/templates/webeid-login.html b/example/src/main/resources/templates/webeid-login.html index 828dc8ef..68bfd695 100644 --- a/example/src/main/resources/templates/webeid-login.html +++ b/example/src/main/resources/templates/webeid-login.html @@ -1,67 +1,42 @@ - - - Signing you in… - - - - - - + + diff --git a/example/src/main/resources/templates/welcome-with-file-upload-support.html b/example/src/main/resources/templates/welcome-with-file-upload-support.html index f17a01e4..e88725da 100644 --- a/example/src/main/resources/templates/welcome-with-file-upload-support.html +++ b/example/src/main/resources/templates/welcome-with-file-upload-support.html @@ -1,38 +1,36 @@ - + Welcome! - - + +
    -
    +

    -
    +

    Sign a document

    - - + +

    -