sourceafis-java icon indicating copy to clipboard operation
sourceafis-java copied to clipboard

[idea] SourceAFIS as Keycloak plugin

Open delcroip opened this issue 7 months ago • 1 comments

Hi,

In my work (NGO) I got the feeling that there is few or none opensource fingerprint management platform

while looking for one I got the idea that SourceAFIS might be a solution if turned into a keycloak plugin, not knowing that field it might be a silly idea, so here I am asking you if there is obvious reason not to do it.

Is there any reason not to do it ? If not I will try to have a closer llok if time premits.

here LLM code proposed after my prompt:

""" would it be possible to create a keyclaok plugin that implement SourceAFIS and save the template in such way that it would be linked to the user ? """

import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import com.machinezoo.sourceafis.FingerprintTemplate;
import com.machinezoo.sourceafis.FingerprintMatcher;
import javax.ws.rs.core.Response;
import java.util.Base64;

public class FingerprintAuthenticator implements Authenticator {
    private static final String TEMPLATE_ATTRIBUTE = "fingerprint_template";

    @Override
    public void authenticate(AuthenticatorContext context) {
        KeycloakSession session = context.getSession();
        UserModel user = context.getUser();
        RealmModel realm = context.getRealm();

        // Get fingerprint data from client (e.g., via form or API)
        String fingerprintData = context.getHttpRequest().getFormParameters().getFirst("fingerprint_data");
        if (fingerprintData == null) {
            context.failureChallenge(Response.status(400).entity("Fingerprint data missing").build());
            return;
        }

        try {
            // Convert base64 fingerprint data to byte array
            byte[] probeImage = Base64.getDecoder().decode(fingerprintData);
            FingerprintTemplate probe = new FingerprintTemplate(probeImage);

            // Retrieve stored template from user attributes
            String storedTemplateBase64 = user.getFirstAttribute(TEMPLATE_ATTRIBUTE);
            if (storedTemplateBase64 == null) {
                context.failureChallenge(Response.status(400).entity("No fingerprint template enrolled").build());
                return;
            }

            byte[] storedTemplateBytes = Base64.getDecoder().decode(storedTemplateBase64);
            FingerprintTemplate candidate = new FingerprintTemplate(storedTemplateBytes);

            // Match fingerprints using SourceAFIS
            FingerprintMatcher matcher = new FingerprintMatcher(probe);
            double score = matcher.match(candidate);

            // Threshold for successful match (adjust as needed)
            double threshold = 40.0; // SourceAFIS recommends ~40 for 1:1 verification
            if (score >= threshold) {
                context.success();
            } else {
                context.failureChallenge(Response.status(401).entity("Fingerprint verification failed").build());
            }
        } catch (Exception e) {
            context.failureChallenge(Response.status(500).entity("Error processing fingerprint: " + e.getMessage()).build());
        }
    }

    @Override
    public void action(AuthenticatorContext context) {
        // Handle enrollment action
        String fingerprintData = context.getHttpRequest().getFormParameters().getFirst("fingerprint_data");
        if (fingerprintData != null) {
            try {
                // Validate and store fingerprint template
                byte[] templateBytes = Base64.getDecoder().decode(fingerprintData);
                FingerprintTemplate template = new FingerprintTemplate(templateBytes);
                String templateBase64 = Base64.getEncoder().encodeToString(templateBytes);

                // Store template in user attributes (encrypt in production)
                UserModel user = context.getUser();
                user.setSingleAttribute(TEMPLATE_ATTRIBUTE, templateBase64);
                context.success();
            } catch (Exception e) {
                context.failureChallenge(Response.status(500).entity("Error enrolling fingerprint: " + e.getMessage()).build());
            }
        }
    }

    @Override
    public boolean requiresUser() {
        return true;
    }

    @Override
    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
        return user.getFirstAttribute(TEMPLATE_ATTRIBUTE) != null;
    }

    @Override
    public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
        // Require fingerprint enrollment if not configured
        user.addRequiredAction("ENROLL_FINGERPRINT");
    }

    @Override
    public void close() {
        // No resources to close
    }
}

delcroip avatar Jun 05 '25 12:06 delcroip

I don't know anything about Keycloak, so pardon my ignorance if I say something stupid. The way I see this is that there should be core "engine" libraries implementing the algorithm in popular languages and this engine core should be surrounded by tools and libraries that integrate the engine with other software or provide additional functionality. So I think there should be a sourceafis-keycloak repository (or keycloak-sourceafis) that has a dependency on sourceafis-java. I currently have other priorities, but you are free to develop and publish it yourself.

robertvazan avatar Jun 05 '25 15:06 robertvazan