如何用自定义 SPI 替换核心类/功能?

Bre*_*nte 5 java cryptography bouncycastle keycloak post-quantum-cryptography

我正在尝试通过自定义 SPI 添加一些后量子密钥算法(来自 liboqs-java)到 Keycloak。我可以使用我添加的算法生成密钥,但在操作它们时遇到了一些问题。

Keycloak 在使用我的新算法处理密钥时遇到问题...我认为 Java BouncyCastle 无法识别后量子算法(例如 Dilithium2),并且会导致系统崩溃。

我在 BCPemUtilsProvider Keycloak 类上的 JcaPEMWriter BouncyCastle 类上遇到了具体问题。我的解决方案是重写 BCPemUtilsProvider,这样我就可以替换 BouncyCastle 函数,但是为了做到这一点,我需要从 Keycloak 更改核心文件并重新编译整个项目,这对于每个微小的更改都需要花费大量时间。

我想通过 SPI(如果可能)或一些轻量级解决方案来解决这个问题,这样我就可以在实际的时间内进行测试。有没有办法在不重新编译整个 Keycloak 的情况下更改核心功能(或者,也许是我没有看到的另一种解决方案)?

提前致谢!!

顺便说一句,这是密钥生成的代码:

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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

这是我面临的错误:

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
Run Code Online (Sandbox Code Playgroud)

小智 0

Brendon Vicente,自定义类加载器可以帮助您。

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

    }
Run Code Online (Sandbox Code Playgroud)