Copy import co.arculus.fido.ArculusFido;
import co.arculus.fido.authenticate.AuthenticateCardSignResponse;
import co.arculus.fido.options.ArculusFidoOptionsAuthorizationResponse;
import com.arculus.desktop.backend.BackendApiClient;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class DesktopFidoApp {
private ArculusFido arculusFido;
private BackendApiClient backendClient;
public DesktopFidoApp(String backendUrl) {
// Initialize SDK with PC/SC reader
arculusFido = new ArculusFido();
// Initialize backend client
backendClient = new BackendApiClient(backendUrl);
}
public void registerUser(String pin, String username, String displayName, String rpId) {
try {
// Phase 1: Backend gets challenge
// Note: If displayName is null, it will default to username
JsonObject backendResponse = backendClient.registerBegin(
username,
displayName != null ? displayName : username,
rpId
);
if (!backendResponse.has("status") ||
!"success".equals(backendResponse.get("status").getAsString())) {
// Error messages may be in "message" or "errorMessage" field
String errorMsg = backendResponse.has("message") ?
backendResponse.get("message").getAsString() : "Backend registration begin failed";
System.err.println(errorMsg);
return;
}
JsonObject fidoServerResponse = backendResponse.getAsJsonObject("fidoServerResponse");
if (fidoServerResponse == null) {
System.err.println("Backend response missing fidoServerResponse");
return;
}
String challenge = fidoServerResponse.has("challenge") ?
fidoServerResponse.get("challenge").getAsString() : null;
if (challenge == null) {
System.err.println("Backend response missing challenge");
return;
}
// Extract rpId from server response if available (CRITICAL: must match what server expects)
String rpIdFromServer = rpId; // Fallback to provided value
if (fidoServerResponse.has("rp")) {
JsonObject rpObject = fidoServerResponse.getAsJsonObject("rp");
if (rpObject != null && rpObject.has("id")) {
rpIdFromServer = rpObject.get("id").getAsString();
}
}
// Extract fido2ServerOrigin (required for clientDataJSON)
String fido2ServerOrigin = backendResponse.has("fido2ServerOrigin") ?
backendResponse.get("fido2ServerOrigin").getAsString() : null;
if (fido2ServerOrigin == null) {
// Fallback: extract from backend URL if not provided
String backendUrl = backendClient.getBaseUrl();
fido2ServerOrigin = backendUrl.replaceAll("/$", "");
}
// Note: Session cookies are automatically stored by backendClient.registerBegin()
// Phase 2: Card operations only
// registerCardOnly() takes challenge and origin directly (not optionsResponse)
String cardResponseJson = arculusFido.registerCardOnly(
pin,
username,
displayName != null ? displayName : username,
rpIdFromServer, // Use rpId from server response
challenge, // Base64-encoded challenge from backend
fido2ServerOrigin // Origin URL for clientDataJSON
);
if (cardResponseJson == null || cardResponseJson.trim().isEmpty()) {
System.err.println("Card registration returned null or empty response");
return;
}
JsonObject cardResponseData = JsonParser.parseString(cardResponseJson).getAsJsonObject();
// Extract credential ID for potential rollback
String credentialId = null;
if (cardResponseData.has("id")) {
credentialId = cardResponseData.get("id").getAsString();
}
// Phase 3: Backend completes registration
// Note: Session cookies are automatically included from Phase 1
JsonObject completeResponse = backendClient.registerComplete(
username,
cardResponseData
);
if (completeResponse.has("status") &&
"success".equals(completeResponse.get("status").getAsString())) {
System.out.println("Registration successful!");
} else {
// Registration failed - rollback: delete credential from card
// Error messages may be in "message" or "errorMessage" field
String errorMsg = completeResponse.has("message") ?
completeResponse.get("message").getAsString() :
(completeResponse.has("errorMessage") ?
completeResponse.get("errorMessage").getAsString() : "Registration failed");
// Perform rollback if credential was created
if (credentialId != null) {
try {
arculusFido.deleteCredentialByCredentialId(pin, credentialId);
} catch (Exception rollbackError) {
errorMsg += " (Warning: Credential may still exist on card - rollback failed)";
}
}
System.err.println("Registration failed: " + errorMsg);
}
} catch (java.io.IOException e) {
// Backend request failed - rollback needed if card operation succeeded
System.err.println("Registration failed: Network error - " +
(e.getMessage() != null ? e.getMessage() : "Connection failed"));
} catch (Exception e) {
System.err.println("Error during registration: " +
(e.getMessage() != null ? e.getMessage() : "Unknown error"));
e.printStackTrace();
}
}
public void authenticateUser(String pin, String username, String rpId) {
try {
// Phase 1: Backend gets challenge
JsonObject backendResponse = backendClient.authenticateBegin(username, rpId);
if (!backendResponse.has("status") ||
!"success".equals(backendResponse.get("status").getAsString())) {
// Error messages may be in "message" or "errorMessage" field
String errorMsg = backendResponse.has("message") ?
backendResponse.get("message").getAsString() : "Backend authentication begin failed";
System.err.println(errorMsg);
return;
}
JsonObject fidoServerResponse = backendResponse.getAsJsonObject("fidoServerResponse");
if (fidoServerResponse == null) {
System.err.println("Backend response missing fidoServerResponse");
return;
}
String challenge = fidoServerResponse.has("challenge") ?
fidoServerResponse.get("challenge").getAsString() : null;
if (challenge == null) {
System.err.println("Backend response missing challenge");
return;
}
// Extract rpId from server response if available (CRITICAL: must match what server expects)
String rpIdFromServer = rpId; // Fallback to provided value
if (fidoServerResponse.has("rpId")) {
rpIdFromServer = fidoServerResponse.get("rpId").getAsString();
}
// Extract fido2ServerOrigin (required for clientDataJSON)
String fido2ServerOrigin = backendResponse.has("fido2ServerOrigin") ?
backendResponse.get("fido2ServerOrigin").getAsString() : null;
if (fido2ServerOrigin == null) {
// Fallback: extract from backend URL if not provided
String backendUrl = backendClient.getBaseUrl();
fido2ServerOrigin = backendUrl.replaceAll("/$", "");
}
String sessionId = backendResponse.has("sessionId") ?
backendResponse.get("sessionId").getAsString() : null;
// Note: Session cookies are automatically stored by backendClient.authenticateBegin()
// Phase 2: Card operations only
ArculusFidoOptionsAuthorizationResponse optionsResponse =
FidoOptionsHelper.createOptionsResponseFromBackend(
fidoServerResponse,
challenge,
rpIdFromServer, // Use rpId from server response
fido2ServerOrigin
);
// Perform card-only authentication
AuthenticateCardSignResponse cardResponse = arculusFido.authenticateCardOnly(
pin,
username,
rpIdFromServer, // Use rpId from server response
optionsResponse
);
// Build FIDO2 authentication response
JsonObject fido2Response = new JsonObject();
fido2Response.addProperty("type", "public-key");
fido2Response.addProperty("id", cardResponse.getId());
fido2Response.addProperty("rawId", cardResponse.getRawId());
JsonObject responseData = new JsonObject();
responseData.addProperty("authenticatorData", cardResponse.getAuthdataBase64());
responseData.addProperty("signature", cardResponse.getSignatureBase64());
responseData.addProperty("clientDataJSON", optionsResponse.getClientDataJSONbase64());
fido2Response.add("response", responseData);
// Phase 3: Backend completes authentication
JsonObject completeResponse = backendClient.authenticateComplete(
fido2Response,
sessionId,
username
);
if (completeResponse.has("status") &&
"success".equals(completeResponse.get("status").getAsString())) {
System.out.println("Authentication successful!");
} else {
// Error messages may be in "message" or "errorMessage" field
String errorMsg = completeResponse.has("message") ?
completeResponse.get("message").getAsString() :
(completeResponse.has("errorMessage") ?
completeResponse.get("errorMessage").getAsString() : "Authentication failed");
System.err.println("Authentication failed: " + errorMsg);
}
} catch (java.io.IOException e) {
System.err.println("Authentication failed: Network error - " +
(e.getMessage() != null ? e.getMessage() : "Connection failed"));
} catch (Exception e) {
System.err.println("Error during authentication: " +
(e.getMessage() != null ? e.getMessage() : "Unknown error"));
e.printStackTrace();
}
}
}