Skip to content

Commit

Permalink
Added mobile tiqr flow integration test
Browse files Browse the repository at this point in the history
  • Loading branch information
oharsta committed Feb 5, 2025
1 parent 4abb36b commit 82a18d2
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,13 @@
import tiqr.org.model.Enrollment;
import tiqr.org.model.Registration;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
Expand All @@ -62,6 +66,7 @@

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static io.restassured.RestAssured.given;
import static java.nio.charset.StandardCharsets.UTF_8;
import static myconext.security.GuestIdpAuthenticationRequestFilter.BROWSER_SESSION_COOKIE_NAME;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
Expand Down Expand Up @@ -345,4 +350,17 @@ protected void clearExternalAccounts(String email) {
userRepository.save(user);

}

protected String decryptRegistrationSecret(String encryptedSecret) throws Exception {
MessageDigest sha = MessageDigest.getInstance("SHA-256");
String secret = "secret";
byte[] digest = sha.digest(secret.getBytes(UTF_8));
SecretKeySpec secretKey = new SecretKeySpec(Arrays.copyOf(digest, 32), "AES");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, secret.getBytes(UTF_8));
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
return new String(cipher.doFinal(java.util.Base64.getDecoder().decode(encryptedSecret)));
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package myconext.tiqr;

import com.github.tomakehurst.wiremock.junit.WireMockRule;
import io.restassured.common.mapper.TypeRef;
import io.restassured.filter.cookie.CookieFilter;
import io.restassured.http.ContentType;
import io.restassured.http.Headers;
import io.restassured.response.Response;
import myconext.AbstractIntegrationTest;
import myconext.model.MagicLinkRequest;
import myconext.model.SamlAuthenticationRequest;
import myconext.model.User;
import org.apache.commons.io.IOUtils;
import org.junit.ClassRule;
import org.junit.Test;
import org.springframework.http.HttpMethod;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import tiqr.org.model.*;
import tiqr.org.secure.Challenge;
import tiqr.org.secure.OCRA;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static io.restassured.RestAssured.given;
import static java.nio.charset.StandardCharsets.UTF_8;
import static myconext.security.GuestIdpAuthenticationRequestFilter.BROWSER_SESSION_COOKIE_NAME;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.*;

@SuppressWarnings("unchecked")
public class TiqrControllerFlowTest extends AbstractIntegrationTest {

@ClassRule
public static WireMockRule wireMockRule = new WireMockRule(8098);

@Test
public void tiqrMobileFlow() throws Exception {
Map<String, String> body = given()
.redirects().follow(false)
.when()
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
.auth().oauth2(opaqueAccessToken(true, "eduid.nl/mobile"))
.get("/mobile/tiqr/sp/start-enrollment")
.body().as(new TypeRef<>() {
});

String enrollmentKey = body.get("enrollmentKey");
String metaDataUrl = String.format("http://localhost:8081/tiqr/metadata?enrollment_key=%s", enrollmentKey);
String url = String.format("https://eduid.nl/tiqrenroll/?metadata=%s", URLEncoder.encode(metaDataUrl, Charset.defaultCharset()));
assertEquals(url, body.get("url"));
assertTrue(body.get("qrcode").startsWith("data:image/png;base64,"));

String enrollmentStatus = given()
.queryParam("enrollmentKey", enrollmentKey)
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
.auth().oauth2(opaqueAccessToken(true, "eduid.nl/mobile"))
.get("/mobile/tiqr/poll-enrollment")
.as(String.class);
assertEquals(EnrollmentStatus.INITIALIZED.name(), enrollmentStatus);

MetaData metaData = given()
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
.auth().oauth2(opaqueAccessToken(true, "eduid.nl/mobile"))
.queryParam("enrollment_key", enrollmentKey)
.get("/mobile/tiqr/metadata")
.as(MetaData.class);
assertEquals("John Doe", metaData.getIdentity().getDisplayName());

enrollmentStatus = given()
.queryParam("enrollmentKey", enrollmentKey)
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
.auth().oauth2(opaqueAccessToken(true, "eduid.nl/mobile"))
.get("/mobile/tiqr/poll-enrollment")
.as(String.class);
assertEquals(EnrollmentStatus.RETRIEVED.name(), enrollmentStatus);

UriComponents uriComponents = UriComponentsBuilder.fromUriString(metaData.getService().getEnrollmentUrl()).build();
String enrollmentSecret = uriComponents.getQueryParams().getFirst("enrollment_secret");

String sessionKey = Challenge.generateSessionKey();
given()
.queryParam("enrollment_secret", enrollmentSecret)
.auth().oauth2(opaqueAccessToken(true, "eduid.nl/mobile"))
.contentType(ContentType.URLENC)
.formParam("userid", metaData.getIdentity().getIdentifier())
.formParam("secret", sessionKey)
.formParam("language", "en")
.formParam("notificationType", "APNS")
.formParam("notificationAddress", "1234567890")
.formParam("version", "2")
.formParam("operation", "register")
.post("/mobile/tiqr/enrollment")
.then()
.statusCode(200);

enrollmentStatus = given()
.queryParam("enrollmentKey", enrollmentKey)
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
.auth().oauth2(opaqueAccessToken(true, "eduid.nl/mobile"))
.get("/mobile/tiqr/poll-enrollment")
.as(String.class);
assertEquals(EnrollmentStatus.PROCESSED.name(), enrollmentStatus);

String phoneNumber = "0612345678";
given()
.when()
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
.auth().oauth2(opaqueAccessToken(true, "eduid.nl/mobile"))
.body(Map.of("phoneNumber", phoneNumber))
.post("/mobile/tiqr/sp/send-phone-code")
.body().as(new TypeRef<>() {
});

User user = userRepository.findUserByEmail("[email protected]").get();
String phoneVerification = (String) user.getSurfSecureId().get(SURFSecureID.PHONE_VERIFICATION_CODE);

given()
.when()
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
.auth().oauth2(opaqueAccessToken(true, "eduid.nl/mobile"))
.body(Map.of("phoneVerification", phoneVerification))
.post("/mobile/tiqr/sp/verify-phone-code")
.body().as(new TypeRef<>() {
});

Map<String, String> startAuthentication = given()
.when()
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
.auth().oauth2(opaqueAccessToken(true, "eduid.nl/mobile"))
.post("/mobile/tiqr/sp/start-authentication")
.body().as(new TypeRef<>() {
});
String generatedSessionKey = startAuthentication.get("sessionKey");

Registration registration = registrationRepository.findRegistrationByUserId(user.getId()).get();
String decryptedSecret = this.decryptRegistrationSecret(registration.getSecret());
Authentication authentication = authenticationRepository.findAuthenticationBySessionKey(generatedSessionKey).get();

String ocra = OCRA.generateOCRA(decryptedSecret, authentication.getChallenge(), generatedSessionKey);

given()
.contentType(ContentType.URLENC)
.accept(ContentType.JSON)
.auth().oauth2(opaqueAccessToken(true, "eduid.nl/mobile"))
.formParam("sessionKey", generatedSessionKey)
.formParam("userId", metaData.getIdentity().getIdentifier())
.formParam("response", ocra)
.formParam("language", "en")
.formParam("operation", "login")
.formParam("notificationType", "APNS")
.formParam("notificationAddress", "1234567890")
.post("/mobile/tiqr/authentication")
.then()
.statusCode(200)
.body(equalTo("OK"));

Map<String, String> newStatus = given()
.queryParam("sessionKey", generatedSessionKey)
.accept(ContentType.JSON)
.contentType(ContentType.JSON)
.auth().oauth2(opaqueAccessToken(true, "eduid.nl/mobile"))
.get("/mobile/tiqr/sp/poll-authentication")
.as(new TypeRef<>() {
});
assertEquals(AuthenticationStatus.SUCCESS.name(), newStatus.get("status"));

}


}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import static io.restassured.RestAssured.given;
import static java.nio.charset.StandardCharsets.UTF_8;
import static myconext.security.GuestIdpAuthenticationRequestFilter.BROWSER_SESSION_COOKIE_NAME;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.*;

@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -343,7 +344,6 @@ private void doStartAuthentication(boolean useRegistrationId) throws Exception {
Authentication authentication = authenticationRepository.findAuthenticationBySessionKey(sessionKey).get();

Registration registration = registrationRepository.findRegistrationByUserId(samlAuthenticationRequest.getUserId()).get();
registration = registrationRepository.findById(registration.getId()).get();
String decryptedSecret = this.decryptRegistrationSecret(registration.getSecret());
String ocra = OCRA.generateOCRA(decryptedSecret, authentication.getChallenge(), sessionKey);

Expand All @@ -359,7 +359,8 @@ private void doStartAuthentication(boolean useRegistrationId) throws Exception {
.formParam("notificationAddress", "1234567890")
.post("/tiqr/authentication")
.then()
.statusCode(200);
.statusCode(200)
.body(equalTo("OK"));

Map<String, String> newStatus = given()
.queryParam("sessionKey", sessionKey)
Expand Down Expand Up @@ -668,17 +669,6 @@ private void doFollowUpEnrollment(Map<String, String> body) {
assertEquals(EnrollmentStatus.PROCESSED.name(), enrollmentStatus);
}

private String decryptRegistrationSecret(String encryptedSecret) throws Exception {
MessageDigest sha = MessageDigest.getInstance("SHA-256");
String secret = "secret";
byte[] digest = sha.digest(secret.getBytes(UTF_8));
SecretKeySpec secretKey = new SecretKeySpec(Arrays.copyOf(digest, 32), "AES");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, secret.getBytes(UTF_8));
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
return new String(cipher.doFinal(Base64.getDecoder().decode(encryptedSecret)));
}

private Map<String, String> spStartAuthentication(CookieFilter cookieFilter, boolean finishAuthentication) throws IOException {
doEnrollmment(true);
//To start an authentication, we need a valid registration
Expand Down

0 comments on commit 82a18d2

Please sign in to comment.