Skip to content

Commit d7e085f

Browse files
NFC-82 NFC signing support for web-eid example
Signed-off-by: Sander Kondratjev <[email protected]>
1 parent 379d320 commit d7e085f

File tree

10 files changed

+292
-46
lines changed

10 files changed

+292
-46
lines changed

example/src/main/java/eu/webeid/example/config/ApplicationConfiguration.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import eu.webeid.security.challenge.ChallengeNonceGenerator;
3131
import org.springframework.context.annotation.Bean;
3232
import org.springframework.context.annotation.Configuration;
33+
import org.springframework.http.HttpMethod;
3334
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
3435
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
3536
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -38,11 +39,13 @@
3839
import org.springframework.security.web.SecurityFilterChain;
3940
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
4041
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
42+
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
43+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
4144

4245
@Configuration
4346
@EnableWebSecurity
4447
@EnableMethodSecurity(securedEnabled = true)
45-
public class ApplicationConfiguration {
48+
public class ApplicationConfiguration implements WebMvcConfigurer {
4649

4750
@Bean
4851
public SecurityFilterChain filterChain(
@@ -66,4 +69,10 @@ public SecurityFilterChain filterChain(
6669
.headers(h -> h.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
6770
.build();
6871
}
72+
73+
@Override
74+
public void addViewControllers(ViewControllerRegistry registry) {
75+
registry.addViewController("/sign/mobile/certificate").setViewName("welcome");
76+
registry.addViewController("/sign/mobile/signature").setViewName("welcome");
77+
}
6978
}

example/src/main/java/eu/webeid/example/config/YAMLConfig.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,17 @@ public class YAMLConfig {
4747
@Value("#{new Boolean('${web-eid-auth-token.validation.use-digidoc4j-prod-configuration}'.trim())}")
4848
private Boolean useDigiDoc4jProdConfiguration;
4949

50+
@Value("#{new Boolean('${web-eid-auth-token.request-signing-cert:false}')}")
51+
private Boolean requestSigningCert;
52+
53+
public Boolean getRequestSigningCert() {
54+
return requestSigningCert;
55+
}
56+
57+
public void setRequestSigningCert(Boolean requestSigningCert) {
58+
this.requestSigningCert = requestSigningCert;
59+
}
60+
5061
public String getLocalOrigin() {
5162
return localOrigin;
5263
}

example/src/main/java/eu/webeid/example/security/WebEidAuthentication.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222

2323
package eu.webeid.example.security;
2424

25+
import eu.webeid.security.authtoken.SupportedSignatureAlgorithm;
26+
import eu.webeid.security.authtoken.WebEidAuthToken;
2527
import eu.webeid.security.certificate.CertificateData;
28+
import org.springframework.lang.Nullable;
2629
import org.springframework.security.core.Authentication;
2730
import org.springframework.security.core.GrantedAuthority;
2831
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
@@ -36,21 +39,40 @@
3639
public class WebEidAuthentication extends PreAuthenticatedAuthenticationToken implements Authentication {
3740

3841
private final String idCode;
42+
private final transient String signingCertificate;
43+
private final transient List<SupportedSignatureAlgorithm> supportedSignatureAlgorithms;
3944

40-
public static Authentication fromCertificate(X509Certificate userCertificate, List<GrantedAuthority> authorities) throws CertificateEncodingException {
45+
public static Authentication fromCertificate(@Nullable WebEidAuthToken authToken, X509Certificate userCertificate, List<GrantedAuthority> authorities) throws CertificateEncodingException {
4146
final String principalName = getPrincipalNameFromCertificate(userCertificate);
4247
final String idCode = CertificateData.getSubjectIdCode(userCertificate)
4348
.orElseThrow(() -> new CertificateEncodingException("Certificate does not contain subject ID code"));
44-
return new WebEidAuthentication(principalName, idCode, authorities);
49+
50+
final String signingCertificate = Optional.ofNullable(authToken)
51+
.map(WebEidAuthToken::getUnverifiedSigningCertificate)
52+
.orElse(null);
53+
54+
final List<SupportedSignatureAlgorithm> supportedSignatureAlgorithms = Optional.ofNullable(authToken)
55+
.map(WebEidAuthToken::getSupportedSignatureAlgorithms)
56+
.orElse(null);
57+
58+
return new WebEidAuthentication(principalName, idCode, signingCertificate, supportedSignatureAlgorithms, authorities);
4559
}
4660

4761
public String getIdCode() {
4862
return idCode;
4963
}
64+
public String getSigningCertificate() {
65+
return signingCertificate;
66+
}
67+
public List<SupportedSignatureAlgorithm> getSupportedSignatureAlgorithms() {
68+
return supportedSignatureAlgorithms;
69+
}
5070

51-
private WebEidAuthentication(String principalName, String idCode, List<GrantedAuthority> authorities) {
71+
private WebEidAuthentication(String principalName, String idCode, String signingCertificate, List<SupportedSignatureAlgorithm> supportedSignatureAlgorithms, List<GrantedAuthority> authorities) {
5272
super(principalName, idCode, authorities);
5373
this.idCode = idCode;
74+
this.signingCertificate = signingCertificate;
75+
this.supportedSignatureAlgorithms = supportedSignatureAlgorithms;
5476
}
5577

5678
private static String getPrincipalNameFromCertificate(X509Certificate userCertificate) throws CertificateEncodingException {

example/src/main/java/eu/webeid/example/security/WebEidAuthenticationProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public Authentication authenticate(Authentication auth) throws AuthenticationExc
7272
try {
7373
final String nonce = challengeNonceStore.getAndRemove().getBase64EncodedNonce();
7474
final X509Certificate userCertificate = tokenValidator.validate(authToken, nonce);
75-
return WebEidAuthentication.fromCertificate(userCertificate, authorities);
75+
return WebEidAuthentication.fromCertificate(authToken, userCertificate, authorities);
7676
} catch (AuthTokenException e) {
7777
throw new AuthenticationServiceException("Web eID token validation failed", e);
7878
} catch (CertificateEncodingException e) {

example/src/main/java/eu/webeid/example/service/SigningService.java

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,15 @@
2222

2323
package eu.webeid.example.service;
2424

25+
import com.fasterxml.jackson.databind.ObjectMapper;
2526
import eu.webeid.example.config.YAMLConfig;
2627
import eu.webeid.example.security.WebEidAuthentication;
2728
import eu.webeid.example.service.dto.CertificateDTO;
2829
import eu.webeid.example.service.dto.DigestDTO;
2930
import eu.webeid.example.service.dto.FileDTO;
31+
import eu.webeid.example.service.dto.SignatureAlgorithmDTO;
3032
import eu.webeid.example.service.dto.SignatureDTO;
33+
import eu.webeid.security.authtoken.SupportedSignatureAlgorithm;
3134
import eu.webeid.security.certificate.CertificateData;
3235
import jakarta.servlet.http.HttpSession;
3336
import jakarta.xml.bind.DatatypeConverter;
@@ -48,12 +51,16 @@
4851
import org.springframework.core.io.ByteArrayResource;
4952
import org.springframework.security.access.annotation.Secured;
5053
import org.springframework.stereotype.Service;
54+
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
5155

5256
import java.io.IOException;
5357
import java.io.InputStream;
5458
import java.security.NoSuchAlgorithmException;
5559
import java.security.cert.CertificateException;
5660
import java.security.cert.X509Certificate;
61+
import java.util.Base64;
62+
import java.util.List;
63+
import java.util.Map;
5764
import java.util.Objects;
5865

5966
import static eu.webeid.example.security.WebEidAuthenticationProvider.ROLE_USER;
@@ -65,13 +72,17 @@ public class SigningService {
6572
private static final String SESSION_ATTR_FILE = "file-to-sign";
6673
private static final String SESSION_ATTR_CONTAINER = "container-to-sign";
6774
private static final String SESSION_ATTR_DATA = "data-to-sign";
75+
public static final String CERTIFICATE_URI = "/sign/mobile/certificate";
76+
public static final String SIGNATURE_URI = "/sign/mobile/signature";
6877
private static final Logger LOG = LoggerFactory.getLogger(SigningService.class);
6978
private final Configuration signingConfiguration;
7079

7180
private final ObjectFactory<HttpSession> httpSessionFactory;
81+
private final YAMLConfig yamlConfig;
7282

7383
public SigningService(ObjectFactory<HttpSession> httpSessionFactory, YAMLConfig yamlConfig) {
7484
this.httpSessionFactory = httpSessionFactory;
85+
this.yamlConfig = yamlConfig;
7586
signingConfiguration = Configuration.of(yamlConfig.getUseDigiDoc4jProdConfiguration() ?
7687
Configuration.Mode.PROD : Configuration.Mode.TEST);
7788
// Use automatic AIA OCSP URL selection from certificate for signatures.
@@ -179,6 +190,81 @@ private Container getContainerToSign(FileDTO fileDTO) {
179190
.build();
180191
}
181192

193+
public Map<String, Object> buildMobileInitResponse(WebEidAuthentication authentication) throws IOException, CertificateException, NoSuchAlgorithmException {
194+
String signingCertificate = authentication.getSigningCertificate();
195+
var supportedSignatureAlgorithms = authentication.getSupportedSignatureAlgorithms();
196+
197+
boolean hasCertificateData = signingCertificate != null
198+
&& supportedSignatureAlgorithms != null
199+
&& !supportedSignatureAlgorithms.isEmpty();
200+
201+
boolean requestSigningCert = yamlConfig.getRequestSigningCert();
202+
String nextPath = requestSigningCert
203+
? CERTIFICATE_URI
204+
: SIGNATURE_URI;
205+
206+
if (!requestSigningCert && hasCertificateData) {
207+
CertificateDTO certificateDTO = new CertificateDTO();
208+
certificateDTO.setCertificate(signingCertificate);
209+
certificateDTO.setSupportedSignatureAlgorithms(mapSupportedAlgorithms(supportedSignatureAlgorithms));
210+
prepareContainer(certificateDTO, authentication);
211+
} else {
212+
LOG.info("Skipping container preparation — no certificate available.");
213+
}
214+
215+
String responseUri = ServletUriComponentsBuilder.fromCurrentContextPath()
216+
.path(nextPath)
217+
.build()
218+
.toUriString();
219+
220+
return Map.of("sign_uri", buildDeepLink(Map.of("response_uri", responseUri)));
221+
}
222+
223+
public Map<String, Object> buildMobileCertificateResponse(CertificateDTO certificateDTO, WebEidAuthentication authentication) throws IOException, CertificateException, NoSuchAlgorithmException {
224+
certificateDTO.setSupportedSignatureAlgorithms(mapSupportedAlgorithms(certificateDTO.getSupportedSignatureAlgorithms()));
225+
DigestDTO digest = prepareContainer(certificateDTO, authentication);
226+
String responseUri = ServletUriComponentsBuilder.fromCurrentContextPath()
227+
.path(SIGNATURE_URI)
228+
.build()
229+
.toUriString();
230+
231+
Map<String, Object> payload = Map.of(
232+
"response_uri", responseUri,
233+
"sign_certificate", certificateDTO.getCertificate(),
234+
"hash", digest.getHash(),
235+
"hash_function", digest.getHashFunction()
236+
);
237+
238+
return Map.of(
239+
"sign_uri", buildDeepLink(payload),
240+
"hash", digest.getHash(),
241+
"hashFunction", digest.getHashFunction()
242+
);
243+
}
244+
245+
private List<SignatureAlgorithmDTO> mapSupportedAlgorithms(List<?> algorithms) {
246+
if (algorithms == null) return List.of();
247+
return algorithms.stream().map(item -> {
248+
if (item instanceof SignatureAlgorithmDTO dto) {
249+
return dto;
250+
} else if (item instanceof SupportedSignatureAlgorithm ssa) {
251+
var dto = new SignatureAlgorithmDTO();
252+
dto.setCryptoAlgorithm(ssa.getCryptoAlgorithm());
253+
dto.setHashFunction(ssa.getHashFunction());
254+
dto.setPaddingScheme(ssa.getPaddingScheme());
255+
return dto;
256+
} else {
257+
throw new IllegalArgumentException("Unsupported algorithm type: " + item.getClass());
258+
}
259+
}).toList();
260+
}
261+
262+
private String buildDeepLink(Map<String, Object> payload) throws IOException {
263+
String payloadJson = new ObjectMapper().writeValueAsString(payload);
264+
String encoded = Base64.getUrlEncoder().withoutPadding().encodeToString(payloadJson.getBytes());
265+
return "web-eid-mobile://sign#" + encoded;
266+
}
267+
182268
private String generateContainerName(String fileName) {
183269
return FilenameUtils.removeExtension(fileName) + ".asice";
184270
}

example/src/main/java/eu/webeid/example/web/rest/SigningController.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,12 @@
3838
import org.springframework.web.bind.annotation.RequestBody;
3939
import org.springframework.web.bind.annotation.RequestMapping;
4040
import org.springframework.web.bind.annotation.RestController;
41+
import org.springframework.web.servlet.ModelAndView;
4142

4243
import java.io.IOException;
4344
import java.security.NoSuchAlgorithmException;
4445
import java.security.cert.CertificateException;
46+
import java.util.Map;
4547

4648
import static eu.webeid.example.security.WebEidAuthenticationProvider.ROLE_USER;
4749

@@ -66,6 +68,26 @@ public FileDTO sign(@RequestBody SignatureDTO data) {
6668
return signingService.signContainer(data);
6769
}
6870

71+
@PostMapping("mobile/init")
72+
public ResponseEntity<Map<String, Object>> mobileInit(WebEidAuthentication authentication) throws CertificateException, IOException, NoSuchAlgorithmException {
73+
return ResponseEntity.ok(signingService.buildMobileInitResponse(authentication));
74+
}
75+
76+
@PostMapping("mobile/certificate")
77+
public ResponseEntity<Map<String, Object>> mobileCertificate(@RequestBody CertificateDTO certDto, WebEidAuthentication authentication) throws CertificateException, IOException, NoSuchAlgorithmException {
78+
return ResponseEntity.ok(signingService.buildMobileCertificateResponse(certDto, authentication));
79+
}
80+
81+
@PostMapping("mobile/sign")
82+
public ResponseEntity<FileDTO> mobileSign(@RequestBody SignatureDTO signatureDTO) {
83+
return ResponseEntity.ok(signingService.signContainer(signatureDTO));
84+
}
85+
86+
@GetMapping("mobile/certificate")
87+
public ModelAndView showCertificateStep() {
88+
return new ModelAndView("welcome");
89+
}
90+
6991
@GetMapping(value = "download", produces = "application/vnd.etsi.asic-e+zip")
7092
public ResponseEntity<Resource> download() throws IOException {
7193

example/src/main/resources/application-dev.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ web-eid-auth-token:
22
validation:
33
use-digidoc4j-prod-configuration: false
44
local-origin: "https://test.web-eid.eu"
5+
request-signing-cert: false

example/src/main/resources/application-prod.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ web-eid-auth-token:
33
use-digidoc4j-prod-configuration: true
44
local-origin: "https://web-eid.eu"
55
truststore-password: "changeit"
6+
request-signing-cert: false
67
spring:
78
thymeleaf:
89
cache: true

0 commit comments

Comments
 (0)