简体   繁体   中英

How to replace core classes/functionalities with a custom SPI?

I'm trying to add some post quantum key algorithms (from liboqs-java) to Keycloak via custom SPI's. I can generate keys with the algorithms i've added, but I ran into some issues while manipulating them.

Keycloak is having trouble while handling keys with my new algorithms... I think Java BouncyCastle does not recognizes post quantum algorithms (such as Dilithium2) and it causes system to crash.

I'm having issues specifically with JcaPEMWriter BouncyCastle class, on BCPemUtilsProvider Keycloak class. My solution was to rewrite BCPemUtilsProvider so I can replace BouncyCastle functions but, in order to do this, I would need to change a core file from Keycloak and recompile the entire project, what would take an enormous amount of time, for each minor change.

I'd like to fix this via SPI (if possible) or some lightweight solution, so I can do tests in a practical time. Is there any way to change core functionalities without recompiling the entiry Keycloak (or, maybe, another solution I'm not seeing)?

Thanks in advance!!

By the way, here is the code for key generating:

public AbstractGeneratedDLSecretKeyProvider(ComponentModel model, KeyUse use, String type, String algorithm) {
        this.status = KeyStatus.from(model.get(Attributes.ACTIVE_KEY, true), model.get(Attributes.ENABLED_KEY, true));
        this.kid = model.get(Attributes.KID_KEY);
        this.model = model;
        this.use = use;
        this.type = type;
        this.algorithm = algorithm;

        if (model.hasNote(PrivateKey.class.getName()) && model.hasNote(PublicKey.class.getName())) {
            privateKey = model.getNote(PrivateKey.class.getName());
            publicKey = model.getNote(PublicKey.class.getName());
        } else {
            Signature signer = new Signature("Dilithium2");

            signer.generate_keypair();

            privateKey = new DLPrivateKey(signer.export_secret_key());
            publicKey = new DLPublicKey(signer.export_public_key());

            model.setNote(PrivateKey.class.getName(), privateKey);
            model.setNote(PublicKey.class.getName(), publicKey);
        }
    }

And this is the error I'm facing:

2022-08-25 21:06:18,476 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (executor-thread-0) Uncaught server error: org.keycloak.common.util.PemException: java.lang.IllegalArgumentException: failed to construct sequence from byte[]: Extra data detected in stream
    at org.keycloak.crypto.def.BCPemUtilsProvider.encode(BCPemUtilsProvider.java:56)
    at org.keycloak.common.crypto.PemUtilsProvider.encodeKey(PemUtilsProvider.java:129)
    at org.keycloak.common.util.PemUtils.encodeKey(PemUtils.java:98)
    at org.keycloak.services.resources.admin.KeyResource.toKeyMetadataRepresentation(KeyResource.java:83)
    at org.keycloak.services.resources.admin.KeyResource.lambda$getKeyMetadata$0(KeyResource.java:67)
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
    at java.base/java.util.stream.Streams$StreamBuilderImpl.forEachRemaining(Streams.java:411)
    at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:658)
    at java.base/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:274)
    at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
    at org.keycloak.services.resources.admin.KeyResource.getKeyMetadata(KeyResource.java:69)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:170)
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130)
    at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:660)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:524)
    at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:474)
    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:476)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:434)
    at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:192)
    at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:152)
    at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:183)
    at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:152)
    at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:183)
    at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:141)
    at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:32)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:492)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:261)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:161)
    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
    at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:164)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:247)
    at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:73)
    at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:151)
    at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:82)
    at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:42)
    at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1212)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:163)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
    at io.quarkus.vertx.http.runtime.StaticResourcesRecorder$2.handle(StaticResourcesRecorder.java:67)
    at io.quarkus.vertx.http.runtime.StaticResourcesRecorder$2.handle(StaticResourcesRecorder.java:55)
    at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1212)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:163)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
    at io.quarkus.vertx.http.runtime.VertxHttpRecorder$5.handle(VertxHttpRecorder.java:380)
    at io.quarkus.vertx.http.runtime.VertxHttpRecorder$5.handle(VertxHttpRecorder.java:358)
    at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1212)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:163)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
    at org.keycloak.quarkus.runtime.integration.web.QuarkusRequestFilter.lambda$createBlockingHandler$1(QuarkusRequestFilter.java:90)
    at io.vertx.core.impl.ContextImpl.lambda$null$0(ContextImpl.java:159)
    at io.vertx.core.impl.AbstractContext.dispatch(AbstractContext.java:100)
    at io.vertx.core.impl.ContextImpl.lambda$executeBlocking$1(ContextImpl.java:157)
    at io.quarkus.vertx.core.runtime.VertxCoreRecorder$13.runWith(VertxCoreRecorder.java:545)
    at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
    at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
    at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.lang.IllegalArgumentException: failed to construct sequence from byte[]: Extra data detected in stream
    at org.bouncycastle.asn1.ASN1Sequence.getInstance(ASN1Sequence.java:92)
    at org.bouncycastle.asn1.x509.SubjectPublicKeyInfo.getInstance(SubjectPublicKeyInfo.java:43)
    at org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator.convertObject(Unknown Source)
    at org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator.<init>(Unknown Source)
    at org.bouncycastle.openssl.jcajce.JcaPEMWriter.writeObject(Unknown Source)
    at org.bouncycastle.openssl.jcajce.JcaPEMWriter.writeObject(Unknown Source)
    at org.keycloak.crypto.def.BCPemUtilsProvider.encode(BCPemUtilsProvider.java:50)
    ... 68 more

Brendon Vicente, Custom ClassLoader can helper your.

public class LoadClassInfo extends ClassLoader{
    private ClassLoader classLoader;
    private String pack_name;
    public LoadClassInfo(ClassLoader parent,String pack_name) {
        super(parent);
        this.classLoader = parent;
        this.pack_name = pack_name;
    }
    public Class<?> loadClass(String name) throws ClassNotFoundException {
       if(!name.contains(pack_name))return super.loadClass(name);
        try {
            if(!name.matches("(.+)\\.(.+)\\.(.+)"))return null;
            InputStream input = classLoader.getResourceAsStream(String.format("%s.class",name.replace(".","/")));
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int data = input.read();
            while(data != -1){
                buffer.write(data);
                data = input.read();
            }
            input.close();
            byte[] classData = buffer.toByteArray();
            System.out.println("name "+name);
            return defineClass(name,classData, 0, classData.length);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
ScheduledThreadPoolExecutor scheduleds = new ScheduledThreadPoolExecutor(1);
    ClassLoader classLoader;
    String file_path = (classLoader = ClassLoader.getSystemClassLoader())
            .getResource("com.imageutil".replace(".","/")).getPath()
            .substring(1);
    try {
        Map<String,String> maps = Files.list(Paths.get(file_path))
        .filter(x->x.getFileName().toString().endsWith(".class"))
        .collect(Collectors.toMap(m->{
            return m.toString();
        },m->{
            try {
                return String.valueOf(Files.size(m));
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        },(a,b)->a));
        maps.forEach((m,n)->{
            System.out.println(m+" : "+n);
        });
        
        scheduleds.scheduleAtFixedRate(()->{
            try {
                Path[] paths = Files.list(Paths.get(file_path)).toArray(Path[]::new);
                for (Path string : paths) {
                    String class_path = string.toString();
                    String last_time = String.valueOf(Files.size(string));
                    if(Objects.equals(last_time,maps.get(class_path)))continue;
                    String pack_name = class_path.split("classes")[1].replace("\\", ".").substring(1);
                    String class_name = pack_name.substring(0, pack_name.lastIndexOf("."));
                    LoadClassInfo loadClassInfos = new LoadClassInfo(classLoader,"com.imageutil");
                    Class<?> classs = loadClassInfos.loadClass(class_name);
                    classs.newInstance();
                    maps.put(class_path,last_time);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        },1,1,TimeUnit.SECONDS);
        
        
    } catch (IOException e) {
        e.printStackTrace();

    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM