5

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
Olivier
  • 13,283
  • 1
  • 8
  • 24

1 Answers1

0

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();

    }
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Sep 05 '22 at 06:12
  • Sorry, but I'm a little lost here... Where should I put this code? This class doesn't seem to be on Keycloak. How could it help me? – Brendon Vicente Sep 05 '22 at 18:26
  • @BrendonVicente Just looking at this code, i would not use it in any case. A scheduled task every second that scans a set of files in `com.imageutil`? No error handling (`printStacktrace`)? And generally bad code quality. Seems completely irrelevant for your question. – slindenau Sep 06 '22 at 07:22
  • I'm sorry, this is just sample code, which dynamically replaces some core classes in Keycloak through the SPI mechanism. The example code is not perfect, you can improve it! Hope my question has a team for you. – linghuchong Sep 06 '22 at 07:59