Skip to main content

Examples

The Gatling simulation below demonstrates the usage of the officially supported high level methods of the library.

Example gatling simulation java

Example simulation
package ch.nevis.auth.fido.uaf.performancetest;

import ch.nevis.auth.fido.uaf.protocol.Aaid;
import ch.nevis.auth.fido.uaf.protocol.AuthenticationAlgorithm;
import ch.nevis.auth.fido.uaf.protocol.KeyId;
import ch.nevis.auth.fido.uaf.protocol.PublicKeyAlgorithm;
import ch.nevis.auth.fido.uaf.testclient.Client;
import ch.nevis.auth.fido.uaf.testclient.ClientException;
import ch.nevis.auth.fido.uaf.testclient.gatling.SessionHelper;
import ch.nevis.auth.fido.uaf.testclient.gatling.UafClientFeeder;
import ch.nevis.auth.fido.uaf.testclient.gatling.UafHelper;

import ch.nevis.auth.fido.uaf.testclient.util.CryptoUtils;
import io.gatling.javaapi.core.CoreDsl;
import io.gatling.javaapi.core.FeederBuilder;
import io.gatling.javaapi.core.OpenInjectionStep;
import io.gatling.javaapi.core.ScenarioBuilder;
import io.gatling.javaapi.core.Simulation;
import io.gatling.javaapi.http.HttpDsl;
import io.gatling.javaapi.http.HttpProtocolBuilder;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects;


public class ExampleSimulation extends Simulation {

private static Path clientStoreDir = Paths.get("performance-test", "build", "client-store");

private FeederBuilder.FileBased<Object> uafServer =
CoreDsl.jsonFile("uaf_server.json").circular();
private FeederBuilder.Batchable<String> legacyLoginFormParameters =
CoreDsl.csv("legacy_login_form.csv").circular();
private FeederBuilder.Batchable<String> uafClient =
CoreDsl.csv("uaf_client.csv").eager().queue();


// In this In-Band UAF scenario we are using a CSV file based feeder to inject client specific attributes into the
// Gatling session. Using these attributes the UAF helper methods can compose a Client instance and use it during
// the whole scenario. After the scenario, the actual Client state is not saved.
private ScenarioBuilder inBandScenario = CoreDsl.scenario("In-Band Scenario")
.feed(uafServer)
.feed(legacyLoginFormParameters)
.feed(uafClient) // using Gatling's CSV feeder
.exec(UafHelper.legacyLogin())
.exec(UafHelper.registration())
.exec(UafHelper.authentication())
.exec(UafHelper.deregistration())
.exec(UafHelper.status("0000-0000-0000-0000"));


// In this Out-of-Band UAF scenario we are using a custom directory based JSON feeder to inject a complete Client
// instance into the Gatling session.
// In this specific example the instance was generated programmatically and saved as a JSON file by the
// ExampleSimulation#generateTestClient(Path) method at the beginning of the simulation. It was included here only
// for demonstration purposes and in a real simulation this process could be done completely independently of the
// simulation itself.
// After finishing the UAF flows the actual state of the Client is stored. In this example we are saving the state
// into the original input JSON file since the target directory was set the same as the input for the feeder. By
// using a different directory one can avoid overwriting the input state if desired.
private ScenarioBuilder outOfBandScenario = CoreDsl.scenario("Out-of-Band Scenario")
.feed(uafServer)
.feed(legacyLoginFormParameters)
.feed(UafClientFeeder.from(clientStoreDir)) // using a custom directory based JSON feeder
.exec(UafHelper.legacyLogin())
.exec(UafHelper.oobRegistration())
.exec(UafHelper.oobAuthentication())
.exec(UafHelper.oobDeregistration())
.exec(UafHelper.updateClientNotes("Last update: " + Instant.now())) // update client state
.exec(UafHelper.setClientStoreDir(clientStoreDir)) // using the same dir to overwrite input files
.exec(UafHelper.saveClient()); // saving the actual client state

HttpProtocolBuilder httpProtocol = HttpDsl.http
.userAgentHeader("Gatling");

{
setUp(
inBandScenario.injectOpen(OpenInjectionStep.atOnceUsers(1)),
outOfBandScenario.injectOpen(OpenInjectionStep.atOnceUsers(1))
).protocols(httpProtocol);
}

public ExampleSimulation() throws IOException {
}

@Override
public void before() {
generateTestClient(clientStoreDir);
}

private static void generateTestClient(Path targetDir) {
Objects.requireNonNull(targetDir);

try {
AuthenticationAlgorithm signatureAlgorithm = AuthenticationAlgorithm.fromCode(2);
PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.fromCode(257);
KeyPair keyPair = CryptoUtils.createKeyPair(publicKeyAlgorithm, signatureAlgorithm);

Client.Builder clientBuilder = Client.builder()
.username("testuser2")
.legacyPassword("Testuser2!")
.appId("https://www.siven.ch/appID")
.facetId("https://register.siven.ch")
.aaid(Aaid.from("ABCD#ABCD"))
.keyId(KeyId.from(("keyId-2").getBytes(StandardCharsets.UTF_8)))
.signatureAlgorithm(signatureAlgorithm)
.publicKeyAlgorithm(publicKeyAlgorithm)
.keyPair(keyPair)
.policy(null) // using default policy
.deviceId("deviceId-2");

clientBuilder = SessionHelper.addDispatchTarget(clientBuilder, "dispatchTargetName-2",
null, "link", Duration.ofDays(365));

Client client = clientBuilder.build();
Path filePath = targetDir.resolve(SessionHelper.generateFileName(client));
SessionHelper.saveClient(client, filePath);
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
throw new ClientException("Can not create key material", e);
}
}

}

Content of the uaf_server.json file from the example

UAF server configuration
[
{
"nevisProxyBaseUrl": "https://nevisproxy:8443",

"legacyAuthenticationReportName": "Legacy Authentication",
"legacyAuthenticationEndpoint": "/auth/pwd",

"facetsReportName": "Facets Request",
"facetsEndpoint": "/nevisfido/uaf/1.1/facets",

"registrationRequestReportName": "Registration Request",
"registrationRequestEndpoint": "/nevisfido/uaf/1.1/request/registration/",

"registrationReportName": "Registration",
"registrationEndpoint": "/nevisfido/uaf/1.1/registration",

"authenticationRequestReportName": "Authentication Request",
"authenticationRequestEndpoint": "/auth/fidouaf",

"authenticationReportName": "Authentication",
"authenticationEndpoint": "/auth/fidouaf/authenticationresponse/",

"authenticationStatusReportName": "Authentication Status",
"generateSecTokenReportName": "Generate SecToken",
"authenticationStatusEndpoint": "/auth/fidouaf",

"deregistrationRequestReportName": "Deregistration Request",
"deregistrationRequestEndpoint": "/nevisfido/uaf/1.1/request/deregistration/",

"statusServiceReportName": "Status Request",
"statusServiceEndpoint": "/nevisfido/status",

"dispatchRegistrationTokenReportName": "Dispatch Registration Token",
"dispatchRegistrationTokenEndpoint": "/nevisfido/token/dispatch/registration/",

"dispatchAuthenticationTokenReportName": "Dispatch Authentication Token",
"dispatchAuthenticationTokenEndpoint": "/oob/",

"dispatchDeregistrationTokenReportName": "Dispatch Deregistration Token",
"dispatchDeregistrationTokenEndpoint": "/nevisfido/token/dispatch/deregistration/",

"redeemRegistrationTokenReportName": "Redeem Registration Token",
"redeemRegistrationTokenEndpoint": "/nevisfido/token/redeem/registration/",

"redeemAuthenticationTokenReportName": "Redeem Authentication Token",
"redeemAuthenticationTokenEndpoint": "/nevisfido/token/redeem/authentication/",

"redeemDeregistrationTokenReportName": "Redeem Deregistration Token",
"redeemDeregistrationTokenEndpoint": "/nevisfido/token/redeem/deregistration/",

"queryDispatchTargetReportName": "Query Dispatch Target",
"queryDispatchTargetEndpoint": "/oob/",

"oobAuthenticationStatusReportName": "OOB Authentication Status",
"oobGenerateSecTokenReportName": "OOB Generate SecToken",
"oobAuthenticationStatusEndpoint": "/oob/"
}
]

Content of the uaf_client.csv file from the example

Example clients
username,legacyPassword,appId,facetId,aaid,keyId,signatureAlgorithm,publicKeyAlgorithm,policy,deviceId,dispatchTargetName,target,dispatcher,dispatchTargetValidityInDays
testuser1,Testuser1!,https://www.siven.ch/appID,https://register.siven.ch,ABCD#ABCD,keyId-1,2,257,,deviceId-1,dispatchTargetName-1,,link,365

Content of the legacy_login_form.csv file from the example

Example legacy login data
formParamUsername,formParamPassword,formLoginExpectedStatusCode
isiwebuserid,isiwebpasswd,200

Content of the programmatically generated and saved test client JSON file from the example

The generateTestClient(Path targetDir) method generated a test client instance which was saved in the testuser2_ABCD#ABCD_keyId-2.json file at the end of the Out-of-Band scenario by the UafHelper.saveClient() call. Below you can find the (truncated) content of that file:

Test client example JSON
{
"username": "testuser2",
"legacyPassword": "Testuser2!",
"aaid": "ABCD#ABCD",
"authenticatorVersion": 1,
"registrationCount": 0,
"signatureAlgorithm": 2,
"publicKeyAlgorithm": 257,
"keyId": "a2V5SWQtMg",
"appId": "https://www.siven.ch/appID",
"facetId": "https://register.siven.ch",
"signatureCount": 0,
"keyPair": "*****",
"policy": null,
"deviceId": "deviceId-2",
"dispatchTargetKeyPair": "*****",
"dispatchTarget": {
"name": "dispatchTargetName-2",
"dispatcher": "link",
"signatureKey": {
"kty": "RSA",
"x5t#S256": "CXDvqJYUZ049R6OWhgXdinjDYi7lx7rAs5F0kfP3FRI",
"nbf": 1699464534,
"e": "AQAB",
"use": "sig",
"kid": "13173646432347910675",
"x5c": [
"MIICwTCCAamgAwIBAgIJALbSMaIcsHITMA0GCSqGSIb3DQEBC ..."
],
"exp": 1731086934,
"n": "sfpg2_Z_8JS6cC2G1JpnEZ5rRPxJ3oztFnGzXyGNbw-0YQ ..."
},
"encryptionKey": {
"kty": "RSA",
"x5t#S256": "CXDvqJYUZ049R6OWhgXdinjDYi7lx7rAs5F0kfP3FRI",
"nbf": 1699464534,
"e": "AQAB",
"use": "enc",
"kid": "13173646432347910675",
"x5c": [
"MIICwTCCAamgAwIBAgIJALbSMaIcsHITMA0GCSqGSIb3DQEBC ..."
],
"exp": 1731086934,
"n": "sfpg2_Z_8JS6cC2G1JpnEZ5rRPxJ3oztFnGzXyGNbw-0YQ ..."
}
},
"notes": "Last update: 2023-11-09T17:28:53.869462Z"
}