Skip to content

Commit 944eb3e

Browse files
NFC-47 NFC support for web-eid example
1 parent 937c55e commit 944eb3e

File tree

5 files changed

+124
-3
lines changed

5 files changed

+124
-3
lines changed

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,16 @@ public ChallengeController(ChallengeNonceGenerator challengeNonceGenerator) {
4040

4141
@GetMapping("challenge")
4242
public ChallengeDTO challenge() {
43-
final ChallengeDTO challenge = new ChallengeDTO();
43+
return generateChallenge();
44+
}
45+
46+
@GetMapping("mobile/challenge")
47+
public ChallengeDTO mobileChallenge() {
48+
return generateChallenge();
49+
}
50+
51+
private ChallengeDTO generateChallenge() {
52+
ChallengeDTO challenge = new ChallengeDTO();
4453
challenge.setNonce(challengeNonceGenerator.generateAndStoreNonce().getBase64EncodedNonce());
4554
return challenge;
4655
}

example/src/main/resources/templates/index.html

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,11 @@ <h3><a id="for-developers"></a>For developers</h3>
265265
authButton.disabled = true;
266266

267267
try {
268-
const challengeResponse = await fetch("/auth/challenge", {
268+
const challengeUrl = isMobileDevice()
269+
? "/auth/mobile/challenge"
270+
: "/auth/challenge";
271+
272+
const challengeResponse = await fetch(challengeUrl, {
269273
method: "GET",
270274
headers: {
271275
"Content-Type": "application/json"
@@ -274,6 +278,22 @@ <h3><a id="for-developers"></a>For developers</h3>
274278
await checkHttpError(challengeResponse);
275279
const {nonce} = await challengeResponse.json();
276280

281+
if (isMobileDevice()) {
282+
const loginUri = encodeURIComponent(window.location.origin + "/");
283+
const payload = {
284+
challenge: nonce,
285+
login_uri: loginUri
286+
};
287+
const encoded = btoa(JSON.stringify(payload));
288+
289+
// TODO: Replace with actual URL once DigiDoc app supports app links
290+
const eidAppUri = `TODO-MOBILE-EID-APP-LAUNCH-URL#${encoded}`;
291+
292+
alert("Opening eID app for authentication...\n\nIf nothing happens, the app may not yet support mobile login.");
293+
window.location.href = eidAppUri;
294+
return;
295+
}
296+
277297
const authToken = await webeid.authenticate(nonce, {lang});
278298

279299
const authTokenResponse = await fetch("/auth/login", {
@@ -299,6 +319,10 @@ <h3><a id="for-developers"></a>For developers</h3>
299319
}
300320
});
301321

322+
function isMobileDevice() {
323+
return /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
324+
}
325+
302326
document.addEventListener('DOMContentLoaded', function () {
303327
setTimeout(function () {
304328
document.querySelector(".eu-logo-fixed").style.display = 'none'

example/src/test/java/eu/webeid/example/WebApplicationTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222

2323
package eu.webeid.example;
2424

25+
import eu.webeid.example.service.SigningService;
26+
import eu.webeid.example.service.dto.FileDTO;
27+
import eu.webeid.example.service.dto.SignatureDTO;
2528
import eu.webeid.example.testutil.Dates;
2629
import eu.webeid.example.testutil.HttpHelper;
2730
import eu.webeid.example.testutil.ObjectMother;
@@ -99,6 +102,14 @@ public void validateOcspResponse(XadesSignature xadesSignature) {
99102
}
100103
};
101104

105+
new MockUp<SigningService>() {
106+
@Mock
107+
public FileDTO signContainer(SignatureDTO signatureDTO) {
108+
// Return a dummy FileDTO in tests
109+
return new FileDTO("example-for-signing.asice");
110+
}
111+
};
112+
102113
MockHttpSession session = new MockHttpSession();
103114
session.setAttribute("challenge-nonce", new ChallengeNonce(ObjectMother.VALID_CHALLENGE_NONCE, DateAndTime.utcNow().plusMinutes(1)));
104115

@@ -132,4 +143,37 @@ public static MockMultipartFile mockMultipartFile() {
132143
assertEquals(HttpStatus.OK.value(), response.getStatus());
133144
assertEquals("attachment; filename=example-for-signing.asice", response.getHeader("Content-Disposition"));
134145
}
146+
147+
@Test
148+
public void testHappyFlow_NfcLoginAuthToken() throws Exception {
149+
new MockUp<SubjectCertificateNotRevokedValidator>() {
150+
@Mock
151+
public void validateCertificateNotRevoked(X509Certificate subjectCertificate) {
152+
// Do not call real OCSP service in tests.
153+
}
154+
};
155+
156+
new MockUp<AsicSignatureFinalizer>() {
157+
@Mock
158+
public void validateOcspResponse(XadesSignature xadesSignature) {
159+
// Do not call real OCSP service in tests.
160+
}
161+
};
162+
163+
new MockUp<SigningService>() {
164+
@Mock
165+
public FileDTO signContainer(SignatureDTO signatureDTO) {
166+
// Return a dummy FileDTO in tests
167+
return new FileDTO("example-for-signing.asice");
168+
}
169+
};
170+
171+
MockHttpSession session = new MockHttpSession();
172+
session.setAttribute("challenge-nonce", new ChallengeNonce(ObjectMother.VALID_CHALLENGE_NONCE, DateAndTime.utcNow().plusMinutes(1)));
173+
174+
MvcResult result = HttpHelper.login(mvcBuilder, session, ObjectMother.mockNfcAuthToken());
175+
MockHttpServletResponse response = result.getResponse();
176+
assertEquals("{\"sub\":\"JAAK-KRISTJAN JÕEORG\",\"auth\":\"[ROLE_USER]\"}", response.getContentAsString());
177+
}
178+
135179
}

example/src/test/java/eu/webeid/example/security/WebEidAjaxLoginProcessingFilterTest.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
package eu.webeid.example.security;
2424

25+
import eu.webeid.example.testutil.ObjectMother;
2526
import jakarta.servlet.http.HttpServletRequest;
2627
import jakarta.servlet.http.HttpServletResponse;
2728
import org.junit.jupiter.api.Test;
@@ -30,6 +31,7 @@
3031

3132
import java.io.BufferedReader;
3233
import java.io.StringReader;
34+
import java.util.Map;
3335

3436
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
3537
import static org.mockito.Mockito.mock;
@@ -58,4 +60,23 @@ void testAttemptAuthentication() throws Exception {
5860
new WebEidAjaxLoginProcessingFilter("/auth/login", authenticationManager)
5961
.attemptAuthentication(request, response));
6062
}
61-
}
63+
64+
@Test
65+
void testAttemptAuthentication_NfcToken() throws Exception {
66+
final HttpServletRequest request = mock(HttpServletRequest.class);
67+
final HttpServletResponse response = mock(HttpServletResponse.class);
68+
when(request.getMethod()).thenReturn(HttpMethod.POST.name());
69+
when(request.getHeader("Content-type")).thenReturn("application/json");
70+
71+
var jsonBody = ObjectMother.toJson(Map.of("auth-token", ObjectMother.mockNfcAuthToken().getToken()));
72+
73+
when(request.getReader()).thenReturn(new BufferedReader(new StringReader(jsonBody)));
74+
75+
final AuthenticationManager authenticationManager = mock(AuthenticationManager.class);
76+
77+
assertDoesNotThrow(() ->
78+
new WebEidAjaxLoginProcessingFilter("/auth/login", authenticationManager)
79+
.attemptAuthentication(request, response)
80+
);
81+
}
82+
}

example/src/test/java/eu/webeid/example/testutil/ObjectMother.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public class ObjectMother {
4848
private static final String TEST_PKI_CONTAINER = "src/test/resources/signout.p12";
4949
private static final String TEST_PKI_CONTAINER_PASSWORD = "test";
5050
private static final WebEidAuthToken VALID_AUTH_TOKEN;
51+
private static final WebEidAuthToken VALID_NFC_AUTH_TOKEN;
5152

5253
static {
5354
try {
@@ -63,6 +64,22 @@ public class ObjectMother {
6364
}
6465
}
6566

67+
static {
68+
try {
69+
VALID_NFC_AUTH_TOKEN = MAPPER.readValue(
70+
"{\"algorithm\":\"ES384\"," +
71+
"\"unverifiedCertificate\":\"MIIEBDCCA2WgAwIBAgIQY5OGshxoPMFg+Wfc0gFEaTAKBggqhkjOPQQDBDBgMQswCQYDVQQGEwJFRTEbMBkGA1UECgwSU0sgSUQgU29sdXRpb25zIEFTMRcwFQYDVQRhDA5OVFJFRS0xMDc0NzAxMzEbMBkGA1UEAwwSVEVTVCBvZiBFU1RFSUQyMDE4MB4XDTIxMDcyMjEyNDMwOFoXDTI2MDcwOTIxNTk1OVowfzELMAkGA1UEBhMCRUUxKjAoBgNVBAMMIUrDlUVPUkcsSkFBSy1LUklTVEpBTiwzODAwMTA4NTcxODEQMA4GA1UEBAwHSsOVRU9SRzEWMBQGA1UEKgwNSkFBSy1LUklTVEpBTjEaMBgGA1UEBRMRUE5PRUUtMzgwMDEwODU3MTgwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQmwEKsJTjaMHSaZj19hb9EJaJlwbKc5VFzmlGMFSJVk4dDy+eUxa5KOA7tWXqzcmhh5SYdv+MxcaQKlKWLMa36pfgv20FpEDb03GCtLqjLTRZ7649PugAQ5EmAqIic29CjggHDMIIBvzAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIDiDBHBgNVHSAEQDA+MDIGCysGAQQBg5EhAQIBMCMwIQYIKwYBBQUHAgEWFWh0dHBzOi8vd3d3LnNrLmVlL0NQUzAIBgYEAI96AQIwHwYDVR0RBBgwFoEUMzgwMDEwODU3MThAZWVzdGkuZWUwHQYDVR0OBBYEFPlp/ceABC52itoqppEmbf71TJz6MGEGCCsGAQUFBwEDBFUwUzBRBgYEAI5GAQUwRzBFFj9odHRwczovL3NrLmVlL2VuL3JlcG9zaXRvcnkvY29uZGl0aW9ucy1mb3ItdXNlLW9mLWNlcnRpZmljYXRlcy8TAkVOMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDAfBgNVHSMEGDAWgBTAhJkpxE6fOwI09pnhClYACCk+ezBzBggrBgEFBQcBAQRnMGUwLAYIKwYBBQUHMAGGIGh0dHA6Ly9haWEuZGVtby5zay5lZS9lc3RlaWQyMDE4MDUGCCsGAQUFBzAChilodHRwOi8vYy5zay5lZS9UZXN0X29mX0VTVEVJRDIwMTguZGVyLmNydDAKBggqhkjOPQQDBAOBjAAwgYgCQgDCAgybz0u3W+tGI+AX+PiI5CrE9ptEHO5eezR1Jo4j7iGaO0i39xTGUB+NSC7P6AQbyE/ywqJjA1a62jTLcS9GHAJCARxN4NO4eVdWU3zVohCXm8WN3DWA7XUcn9TZiLGQ29P4xfQZOXJi/z4PNRRsR4plvSNB3dfyBvZn31HhC7my8woi\"," +
72+
"\"unverifiedSigningCertificate\":\"MIIEBDCCA2WgAwIBAgIQY5OGshxoPMFg+Wfc0gFEaTAKBggqhkjOPQQDBDBgMQswCQYDVQQGEwJFRTEbMBkGA1UECgwSU0sgSUQgU29sdXRpb25zIEFTMRcwFQYDVQRhDA5OVFJFRS0xMDc0NzAxMzEbMBkGA1UEAwwSVEVTVCBvZiBFU1RFSUQyMDE4MB4XDTIxMDcyMjEyNDMwOFoXDTI2MDcwOTIxNTk1OVowfzELMAkGA1UEBhMCRUUxKjAoBgNVBAMMIUrDlUVPUkcsSkFBSy1LUklTVEpBTiwzODAwMTA4NTcxODEQMA4GA1UEBAwHSsOVRU9SRzEWMBQGA1UEKgwNSkFBSy1LUklTVEpBTjEaMBgGA1UEBRMRUE5PRUUtMzgwMDEwODU3MTgwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQmwEKsJTjaMHSaZj19hb9EJaJlwbKc5VFzmlGMFSJVk4dDy+eUxa5KOA7tWXqzcmhh5SYdv+MxcaQKlKWLMa36pfgv20FpEDb03GCtLqjLTRZ7649PugAQ5EmAqIic29CjggHDMIIBvzAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIDiDBHBgNVHSAEQDA+MDIGCysGAQQBg5EhAQIBMCMwIQYIKwYBBQUHAgEWFWh0dHBzOi8vd3d3LnNrLmVlL0NQUzAIBgYEAI96AQIwHwYDVR0RBBgwFoEUMzgwMDEwODU3MThAZWVzdGkuZWUwHQYDVR0OBBYEFPlp/ceABC52itoqppEmbf71TJz6MGEGCCsGAQUFBwEDBFUwUzBRBgYEAI5GAQUwRzBFFj9odHRwczovL3NrLmVlL2VuL3JlcG9zaXRvcnkvY29uZGl0aW9ucy1mb3ItdXNlLW9mLWNlcnRpZmljYXRlcy8TAkVOMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDAfBgNVHSMEGDAWgBTAhJkpxE6fOwI09pnhClYACCk+ezBzBggrBgEFBQcBAQRnMGUwLAYIKwYBBQUHMAGGIGh0dHA6Ly9haWEuZGVtby5zay5lZS9lc3RlaWQyMDE4MDUGCCsGAQUFBzAChilodHRwOi8vYy5zay5lZS9UZXN0X29mX0VTVEVJRDIwMTguZGVyLmNydDAKBggqhkjOPQQDBAOBjAAwgYgCQgDCAgybz0u3W+tGI+AX+PiI5CrE9ptEHO5eezR1Jo4j7iGaO0i39xTGUB+NSC7P6AQbyE/ywqJjA1a62jTLcS9GHAJCARxN4NO4eVdWU3zVohCXm8WN3DWA7XUcn9TZiLGQ29P4xfQZOXJi/z4PNRRsR4plvSNB3dfyBvZn31HhC7my8woi\"," +
73+
"\"supportedSignatureAlgorithms\":[{\"cryptoAlgorithm\":\"RSA\",\"hashFunction\":\"SHA-256\",\"paddingScheme\":\"PKCS1.5\"}]," +
74+
"\"issuerApp\":\"https://web-eid.eu/web-eid-mobile-app/releases/v1.0.0\"," +
75+
"\"signature\":\"0Ov7ME6pTY1K2GXMj8Wxov/o2fGIMEds8OMY5dKdkB0nrqQX7fG1E5mnsbvyHpMDecMUH6Yg+p1HXdgB/lLqOcFZjt/OVXPjAAApC5d1YgRYATDcxsR1zqQwiNcHdmWn\"," +
76+
"\"format\":\"web-eid:1.1\"}",
77+
WebEidAuthToken.class);
78+
} catch (JsonProcessingException e) {
79+
throw new RuntimeException("NFC token parsing failed");
80+
}
81+
}
82+
6683
public static final String VALID_CHALLENGE_NONCE = "12345678123456781234567812345678912356789123";
6784

6885
public static AuthTokenDTO mockAuthToken() {
@@ -71,6 +88,12 @@ public static AuthTokenDTO mockAuthToken() {
7188
return authToken;
7289
}
7390

91+
public static AuthTokenDTO mockNfcAuthToken() {
92+
AuthTokenDTO authToken = new AuthTokenDTO();
93+
authToken.setToken(VALID_NFC_AUTH_TOKEN);
94+
return authToken;
95+
}
96+
7497
public static String toJson(Object object) throws JsonProcessingException {
7598
return MAPPER.writeValueAsString(object);
7699
}

0 commit comments

Comments
 (0)