This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch 3_0_X
in repository https://gitbox.apache.org/repos/asf/syncope.git

commit bd339560cb2506a07f9b0c8b7a770269f8fd472d
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Tue Dec 2 19:39:54 2025 +0100

    [SYNCOPE-1936] Generate OIDC JWKS as CAS does (#1252)
---
 .../apache/syncope/client/console/panels/OIDC.java |  41 ++++---
 .../console/panels/OIDCJWKSGenerationPanel.java    | 117 +++++++++++++++++++
 .../client/console/rest/OIDCJWKSRestClient.java    |   4 +-
 .../apache/syncope/client/console/panels/OIDC.html |   1 +
 .../console/panels/OIDCJWKSGenerationPanel.html    |  27 +++++
 .../apache/syncope/core/logic/AMLogicContext.java  |   8 +-
 .../apache/syncope/core/logic/OIDCJWKSLogic.java   |  67 ++++++++---
 .../core/persistence/jpa/dao/JPAOIDCJWKSDAO.java   |   8 +-
 .../provisioning/api/data/OIDCJWKSDataBinder.java  |  30 +++++
 core/provisioning-java/pom.xml                     |   5 +
 .../java/data/OIDCJWKSDataBinderImpl.java          | 130 ++++++++++-----------
 docker/core/LICENSE                                |   4 +
 pom.xml                                            |   6 +
 .../syncope/wa/starter/config/WAContext.java       |   7 +-
 .../starter/oidc/WAOIDCJWKSGeneratorService.java   |  13 ++-
 15 files changed, 354 insertions(+), 114 deletions(-)

diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDC.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDC.java
index 4bc40ab029..e439c26317 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDC.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDC.java
@@ -24,6 +24,7 @@ import java.io.IOException;
 import java.util.concurrent.atomic.AtomicReference;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.rest.OIDCJWKSRestClient;
+import org.apache.syncope.client.console.rest.WAConfigRestClient;
 import 
org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
 import 
org.apache.syncope.client.console.wicket.markup.html.form.JsonEditorPanel;
 import org.apache.syncope.client.ui.commons.Constants;
@@ -54,6 +55,11 @@ public class OIDC extends Panel {
     @SpringBean
     protected OIDCJWKSRestClient oidcJWKSRestClient;
 
+    @SpringBean
+    protected WAConfigRestClient waConfigRestClient;
+
+    protected final BaseModal<OIDCJWKSTO> generateModal = new 
BaseModal<>("generateModal");
+
     protected final BaseModal<String> viewModal = new BaseModal<>("viewModal") 
{
 
         private static final long serialVersionUID = 389935548143327858L;
@@ -75,15 +81,15 @@ public class OIDC extends Panel {
         super(id);
         setOutputMarkupId(true);
 
-        add(viewModal);
-        viewModal.size(Modal.Size.Extra_large);
-        viewModal.setWindowClosedCallback(target -> viewModal.show(false));
-
         WebMarkupContainer container = new WebMarkupContainer("container");
         add(container.setOutputMarkupId(true));
 
         AtomicReference<OIDCJWKSTO> oidcjwksto = oidcJWKSRestClient.get();
 
+        add(viewModal);
+        viewModal.size(Modal.Size.Extra_large);
+        viewModal.setWindowClosedCallback(target -> viewModal.show(false));
+
         view = new AjaxLink<>("view") {
 
             private static final long serialVersionUID = 6250423506463465679L;
@@ -123,18 +129,10 @@ public class OIDC extends Panel {
 
             @Override
             public void onClick(final AjaxRequestTarget target) {
-                try {
-                    oidcjwksto.set(oidcJWKSRestClient.generate());
-                    generate.setEnabled(false);
-                    view.setEnabled(true);
-
-                    
SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
-                    target.add(container);
-                } catch (Exception e) {
-                    LOG.error("While generating OIDC JWKS", e);
-                    SyncopeConsoleSession.get().onException(e);
-                }
-                ((BaseWebPage) 
pageRef.getPage()).getNotificationPanel().refresh(target);
+                generateModal.header(Model.of("Generate JSON Web Key Sets"));
+                target.add(generateModal.setContent(new 
OIDCJWKSGenerationPanel(
+                        oidcJWKSRestClient, waConfigRestClient, generateModal, 
pageRef)));
+                generateModal.show(true);
             }
 
             @Override
@@ -184,6 +182,17 @@ public class OIDC extends Panel {
         container.add(delete.setOutputMarkupId(true));
         MetaDataRoleAuthorizationStrategy.authorize(delete, ENABLE, 
AMEntitlement.OIDC_JWKS_DELETE);
 
+        generateModal.addSubmitButton();
+        add(generateModal);
+        generateModal.setWindowClosedCallback(target -> {
+            oidcjwksto.set(oidcJWKSRestClient.get().get());
+            view.setEnabled(oidcjwksto.get() != null);
+            delete.setEnabled(oidcjwksto.get() != null);
+
+            target.add(container);
+            generateModal.show(false);
+        });
+
         String wellKnownURI = waPrefix + 
"/oidc/.well-known/openid-configuration";
         container.add(new ExternalLink("wellKnownURI", wellKnownURI, 
wellKnownURI));
     }
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDCJWKSGenerationPanel.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDCJWKSGenerationPanel.java
new file mode 100644
index 0000000000..ae57f86be6
--- /dev/null
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDCJWKSGenerationPanel.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.client.console.panels;
+
+import java.util.List;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.rest.OIDCJWKSRestClient;
+import org.apache.syncope.client.console.rest.WAConfigRestClient;
+import 
org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxEventBehavior;
+import 
org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.ui.commons.Constants;
+import 
org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
+import 
org.apache.syncope.client.ui.commons.markup.html.form.AjaxNumberFieldPanel;
+import 
org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.OIDCJWKSTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.model.Model;
+
+public class OIDCJWKSGenerationPanel extends AbstractModalPanel<OIDCJWKSTO> {
+
+    private static final long serialVersionUID = -3372006007594607067L;
+
+    protected final OIDCJWKSRestClient oidcJWKSRestClient;
+
+    protected final Model<String> jwksKeyIdM;
+
+    protected final Model<String> jwksTypeM;
+
+    protected final Model<Integer> jwksKeySizeM;
+
+    public OIDCJWKSGenerationPanel(
+            final OIDCJWKSRestClient oidcJWKSRestClient,
+            final WAConfigRestClient waConfigRestClient,
+            final BaseModal<OIDCJWKSTO> modal,
+            final PageReference pageRef) {
+
+        super(modal, pageRef);
+        this.oidcJWKSRestClient = oidcJWKSRestClient;
+
+        jwksKeyIdM = Model.of("syncope");
+        try {
+            
jwksKeyIdM.setObject(waConfigRestClient.get("cas.authn.oidc.jwks.core.jwks-key-id").getValues().get(0));
+        } catch (SyncopeClientException e) {
+            LOG.error("While reading cas.authn.oidc.jwks.core.jwks-key-id", e);
+        }
+        add(new AjaxTextFieldPanel("jwksKeyId", "jwksKeyId", 
jwksKeyIdM).setRequired(true));
+
+        jwksTypeM = Model.of("rsa");
+        try {
+            
jwksTypeM.setObject(waConfigRestClient.get("cas.authn.oidc.jwks.core.jwks-type").getValues().get(0));
+        } catch (SyncopeClientException e) {
+            LOG.error("While reading cas.authn.oidc.jwks.core.jwks-type", e);
+        }
+        AjaxDropDownChoicePanel<String> jwksType = new 
AjaxDropDownChoicePanel<>("jwksType", "jwksType", jwksTypeM).
+                setChoices(List.of("rsa", "ec"));
+        add(jwksType.setRequired(true));
+
+        jwksKeySizeM = Model.of(2048);
+        try {
+            jwksKeySizeM.setObject(Integer.valueOf(
+                    
waConfigRestClient.get("cas.authn.oidc.jwks.core.jwks-key-size").getValues().get(0)));
+        } catch (SyncopeClientException e) {
+            LOG.error("While reading cas.authn.oidc.jwks.core.jwks-key-size", 
e);
+        }
+        AjaxNumberFieldPanel<Integer> jwksKeySize = new 
AjaxNumberFieldPanel.Builder<Integer>().step(128).
+                build("jwksKeySize", "jwksKeySize", Integer.class, 
jwksKeySizeM);
+        add(jwksKeySize.setRequired(true));
+
+        jwksType.add(new IndicatorAjaxEventBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -4255753643957306394L;
+
+            @Override
+            protected void onEvent(final AjaxRequestTarget target) {
+                if ("ec".equals(jwksTypeM.getObject())) {
+                    jwksKeySizeM.setObject(256);
+                } else {
+                    jwksKeySizeM.setObject(2048);
+                }
+                target.add(jwksKeySize);
+            }
+        });
+    }
+
+    @Override
+    public void onSubmit(final AjaxRequestTarget target) {
+        try {
+            oidcJWKSRestClient.generate(jwksKeyIdM.getObject(), 
jwksTypeM.getObject(), jwksKeySizeM.getObject());
+
+            
SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
+            modal.close(target);
+        } catch (Exception e) {
+            LOG.error("While generating OIDC JWKS", e);
+            SyncopeConsoleSession.get().onException(e);
+        }
+        ((BaseWebPage) 
pageRef.getPage()).getNotificationPanel().refresh(target);
+    }
+}
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/rest/OIDCJWKSRestClient.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/rest/OIDCJWKSRestClient.java
index 8db5f3ab6d..a7abf44c8b 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/rest/OIDCJWKSRestClient.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/rest/OIDCJWKSRestClient.java
@@ -37,8 +37,8 @@ public class OIDCJWKSRestClient extends BaseRestClient {
         return result;
     }
 
-    public OIDCJWKSTO generate() {
-        Response response = 
getService(OIDCJWKSService.class).generate("syncope", "RSA", 2048);
+    public OIDCJWKSTO generate(final String jwksKeyId, final String jwksType, 
final int jwksKeySize) {
+        Response response = 
getService(OIDCJWKSService.class).generate(jwksKeyId, jwksType, jwksKeySize);
         return response.readEntity(OIDCJWKSTO.class);
     }
 
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC.html
 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC.html
index e95ce63322..0e34db86e3 100644
--- 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC.html
+++ 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC.html
@@ -49,6 +49,7 @@ under the License.
       </div>
     </div>
 
+    <div wicket:id="generateModal"/>
     <div wicket:id="viewModal"/>
   </wicket:panel>
 </html>
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDCJWKSGenerationPanel.html
 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDCJWKSGenerationPanel.html
new file mode 100644
index 0000000000..cce9c66bf5
--- /dev/null
+++ 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDCJWKSGenerationPanel.html
@@ -0,0 +1,27 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"; >
+  <wicket:extend>
+    <div class="form-group">
+      <span wicket:id="jwksKeyId"/>
+      <span wicket:id="jwksType"/>
+      <span wicket:id="jwksKeySize"/>
+    </div>
+  </wicket:extend>
+</html>
diff --git 
a/core/am/logic/src/main/java/org/apache/syncope/core/logic/AMLogicContext.java 
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/AMLogicContext.java
index 8a365cc522..07346e6f58 100644
--- 
a/core/am/logic/src/main/java/org/apache/syncope/core/logic/AMLogicContext.java
+++ 
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/AMLogicContext.java
@@ -114,10 +114,12 @@ public class AMLogicContext {
     @ConditionalOnMissingBean
     @Bean
     public OIDCJWKSLogic oidcJWKSLogic(
-            final OIDCJWKSDataBinder binder,
-            final OIDCJWKSDAO dao) {
+            final OIDCJWKSDataBinder oidcJWKSDataBinder,
+            final OIDCJWKSDAO oidcJWKSDAO,
+            final WAConfigDAO waConfigDAO,
+            final EntityFactory entityFactory) {
 
-        return new OIDCJWKSLogic(binder, dao);
+        return new OIDCJWKSLogic(oidcJWKSDataBinder, oidcJWKSDAO, waConfigDAO, 
entityFactory);
     }
 
     @ConditionalOnMissingBean
diff --git 
a/core/am/logic/src/main/java/org/apache/syncope/core/logic/OIDCJWKSLogic.java 
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/OIDCJWKSLogic.java
index e088d7bbd3..d998beae54 100644
--- 
a/core/am/logic/src/main/java/org/apache/syncope/core/logic/OIDCJWKSLogic.java
+++ 
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/OIDCJWKSLogic.java
@@ -20,13 +20,17 @@ package org.apache.syncope.core.logic;
 
 import java.lang.reflect.Method;
 import java.util.Optional;
+import java.util.List;
 import org.apache.syncope.common.lib.to.OIDCJWKSTO;
 import org.apache.syncope.common.lib.types.AMEntitlement;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.core.persistence.api.dao.DuplicateException;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.persistence.api.dao.OIDCJWKSDAO;
+import org.apache.syncope.core.persistence.api.dao.WAConfigDAO;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.am.OIDCJWKS;
+import org.apache.syncope.core.persistence.api.entity.am.WAConfigEntry;
 import org.apache.syncope.core.provisioning.api.data.OIDCJWKSDataBinder;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
@@ -35,52 +39,81 @@ public class OIDCJWKSLogic extends 
AbstractTransactionalLogic<OIDCJWKSTO> {
 
     protected final OIDCJWKSDataBinder binder;
 
-    protected final OIDCJWKSDAO dao;
+    protected final OIDCJWKSDAO oidcJWKSDAO;
+
+    protected final WAConfigDAO waConfigDAO;
+
+    protected final EntityFactory entityFactory;
+
+    public OIDCJWKSLogic(
+            final OIDCJWKSDataBinder binder,
+            final OIDCJWKSDAO oidcJWKSDAO,
+            final WAConfigDAO waConfigDAO,
+            final EntityFactory entityFactory) {
 
-    public OIDCJWKSLogic(final OIDCJWKSDataBinder binder, final OIDCJWKSDAO 
dao) {
         this.binder = binder;
-        this.dao = dao;
+        this.oidcJWKSDAO = oidcJWKSDAO;
+        this.waConfigDAO = waConfigDAO;
+        this.entityFactory = entityFactory;
     }
 
     @PreAuthorize("hasRole('" + AMEntitlement.OIDC_JWKS_READ + "') "
             + "or hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
     @Transactional(readOnly = true)
     public OIDCJWKSTO get() {
-        return Optional.ofNullable(dao.get()).
+        return Optional.ofNullable(oidcJWKSDAO.get()).
                 map(binder::getOIDCJWKSTO).
                 orElseThrow(() -> new NotFoundException("OIDC JWKS not 
found"));
     }
 
+    @PreAuthorize("hasRole('" + AMEntitlement.OIDC_JWKS_SET + "') "
+            + "or hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+    public OIDCJWKSTO set(final OIDCJWKSTO entityTO) {
+        OIDCJWKS jwks = oidcJWKSDAO.get();
+        jwks.setJson(entityTO.getJson());
+        return binder.getOIDCJWKSTO(oidcJWKSDAO.save(jwks));
+    }
+
     @PreAuthorize("hasRole('" + AMEntitlement.OIDC_JWKS_GENERATE + "') "
             + "or hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
     public OIDCJWKSTO generate(final String jwksKeyId, final String jwksType, 
final int jwksKeySize) {
-        OIDCJWKS jwks = dao.get();
-        if (jwks == null) {
-            return binder.getOIDCJWKSTO(dao.save(binder.create(jwksKeyId, 
jwksType, jwksKeySize)));
+        if (oidcJWKSDAO.get() == null) {
+            OIDCJWKSTO oidcJWKSTO = binder.getOIDCJWKSTO(
+                    oidcJWKSDAO.save(binder.create(jwksKeyId, jwksType, 
jwksKeySize)));
+
+            WAConfigEntry jwksKeyIdConfig = 
entityFactory.newEntity(WAConfigEntry.class);
+            jwksKeyIdConfig.setKey("cas.authn.oidc.jwks.core.jwks-key-id");
+            jwksKeyIdConfig.setValues(List.of(jwksKeyId));
+            waConfigDAO.save(jwksKeyIdConfig);
+
+            WAConfigEntry jwksTypeConfig = 
entityFactory.newEntity(WAConfigEntry.class);
+            jwksTypeConfig.setKey("cas.authn.oidc.jwks.core.jwks-type");
+            jwksTypeConfig.setValues(List.of(jwksType));
+            waConfigDAO.save(jwksTypeConfig);
+
+            WAConfigEntry jwksKeySizeConfig = 
entityFactory.newEntity(WAConfigEntry.class);
+            jwksKeySizeConfig.setKey("cas.authn.oidc.jwks.core.jwks-key-size");
+            jwksKeySizeConfig.setValues(List.of(String.valueOf(jwksKeySize)));
+            waConfigDAO.save(jwksKeySizeConfig);
+
+            return oidcJWKSTO;
         }
+
         throw new DuplicateException("OIDC JWKS already set");
     }
 
     @PreAuthorize("hasRole('" + AMEntitlement.OIDC_JWKS_DELETE + "')")
     public void delete() {
-        dao.delete();
+        oidcJWKSDAO.delete();
     }
 
     @Override
     protected OIDCJWKSTO resolveReference(final Method method, final Object... 
args)
             throws UnresolvedReferenceException {
-        OIDCJWKS jwks = dao.get();
+        OIDCJWKS jwks = oidcJWKSDAO.get();
         if (jwks == null) {
             throw new UnresolvedReferenceException();
         }
         return binder.getOIDCJWKSTO(jwks);
     }
-
-    @PreAuthorize("hasRole('" + AMEntitlement.OIDC_JWKS_SET + "') "
-            + "or hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
-    public OIDCJWKSTO set(final OIDCJWKSTO entityTO) {
-        OIDCJWKS jwks = dao.get();
-        jwks.setJson(entityTO.getJson());
-        return binder.getOIDCJWKSTO(dao.save(jwks));
-    }
 }
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAOIDCJWKSDAO.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAOIDCJWKSDAO.java
index 3e1cd75ee5..2edb514f67 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAOIDCJWKSDAO.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAOIDCJWKSDAO.java
@@ -31,8 +31,8 @@ public class JPAOIDCJWKSDAO extends AbstractDAO<OIDCJWKS> 
implements OIDCJWKSDAO
     @Override
     public OIDCJWKS get() {
         try {
-            TypedQuery<OIDCJWKS> query = entityManager().
-                    createQuery("SELECT e FROM " + 
JPAOIDCJWKS.class.getSimpleName() + " e", OIDCJWKS.class);
+            TypedQuery<OIDCJWKS> query = entityManager().createQuery(
+                    "SELECT e FROM " + JPAOIDCJWKS.class.getSimpleName() + " 
e", OIDCJWKS.class);
             return query.getSingleResult();
         } catch (final NoResultException e) {
             LOG.debug(e.getMessage());
@@ -47,8 +47,6 @@ public class JPAOIDCJWKSDAO extends AbstractDAO<OIDCJWKS> 
implements OIDCJWKSDAO
 
     @Override
     public void delete() {
-        entityManager().
-                createQuery("DELETE FROM " + 
JPAOIDCJWKS.class.getSimpleName()).
-                executeUpdate();
+        entityManager().createQuery("DELETE FROM " + 
JPAOIDCJWKS.class.getSimpleName()).executeUpdate();
     }
 }
diff --git 
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/OIDCJWKSDataBinder.java
 
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/OIDCJWKSDataBinder.java
index 8baf5da9e0..1e80199f41 100644
--- 
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/OIDCJWKSDataBinder.java
+++ 
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/OIDCJWKSDataBinder.java
@@ -23,6 +23,36 @@ import 
org.apache.syncope.core.persistence.api.entity.am.OIDCJWKS;
 
 public interface OIDCJWKSDataBinder {
 
+    String PARAMETER_STATE = "state";
+
+    enum JsonWebKeyLifecycleState {
+        /**
+         * The key state is active and current and is used for crypto 
operations as necessary.
+         * Per the rotation schedule, the key with this status would be 
replaced and rotated by the future key.
+         */
+        CURRENT(0),
+        /**
+         * The key state is one for the future and will take the place of the 
current key per the rotation schedule.
+         */
+        FUTURE(1),
+        /**
+         * Previous key prior to the current key.
+         * This key continues to remain valid and available, and is a 
candidate to be removed from the keystore
+         * per the revocation schedule.
+         */
+        PREVIOUS(2);
+
+        private final long state;
+
+        JsonWebKeyLifecycleState(final long state) {
+            this.state = state;
+        }
+
+        public long getState() {
+            return state;
+        }
+    }
+
     OIDCJWKSTO getOIDCJWKSTO(OIDCJWKS jwks);
 
     OIDCJWKS create(String jwksKeyId, String jwksType, int jwksKeySize);
diff --git a/core/provisioning-java/pom.xml b/core/provisioning-java/pom.xml
index 4e14d40fd0..0c724d9d5a 100644
--- a/core/provisioning-java/pom.xml
+++ b/core/provisioning-java/pom.xml
@@ -63,6 +63,11 @@ under the License.
       <artifactId>geronimo-javamail_1.4_mail</artifactId>
     </dependency>
     
+    <dependency>
+      <groupId>org.bitbucket.b_c</groupId>
+      <artifactId>jose4j</artifactId>
+    </dependency>
+
     <dependency>
       <groupId>com.fasterxml.jackson.dataformat</groupId>
       <artifactId>jackson-dataformat-csv</artifactId>
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCJWKSDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCJWKSDataBinderImpl.java
index c8296be68c..862c83decd 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCJWKSDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCJWKSDataBinderImpl.java
@@ -18,20 +18,9 @@
  */
 package org.apache.syncope.core.provisioning.java.data;
 
-import com.nimbusds.jose.JOSEException;
-import com.nimbusds.jose.jwk.Curve;
-import com.nimbusds.jose.jwk.ECKey;
-import com.nimbusds.jose.jwk.JWK;
-import com.nimbusds.jose.jwk.JWKSet;
-import com.nimbusds.jose.jwk.KeyUse;
-import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
-import com.nimbusds.jose.util.JSONObjectUtils;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.OIDCJWKSTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
@@ -39,6 +28,15 @@ import 
org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.am.OIDCJWKS;
 import org.apache.syncope.core.provisioning.api.data.OIDCJWKSDataBinder;
 import org.apache.syncope.core.spring.security.SecureRandomUtils;
+import org.jose4j.jwk.EcJwkGenerator;
+import org.jose4j.jwk.JsonWebKey;
+import org.jose4j.jwk.JsonWebKeySet;
+import org.jose4j.jwk.PublicJsonWebKey;
+import org.jose4j.jwk.RsaJwkGenerator;
+import org.jose4j.jwk.Use;
+import org.jose4j.jws.AlgorithmIdentifiers;
+import org.jose4j.keys.EllipticCurves;
+import org.jose4j.lang.JoseException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -54,61 +52,59 @@ public class OIDCJWKSDataBinderImpl implements 
OIDCJWKSDataBinder {
 
     @Override
     public OIDCJWKSTO getOIDCJWKSTO(final OIDCJWKS jwks) {
-        return new 
OIDCJWKSTO.Builder().json(jwks.getJson()).key(jwks.getKey()).build();
+        return new OIDCJWKSTO.Builder().
+                key(jwks.getKey()).
+                json(jwks.getJson()).
+                build();
     }
 
-    @Override
-    public OIDCJWKS create(final String jwksKeyId, final String jwksType, 
final int jwksKeySize) {
-        JWK jwk;
-        try {
-            switch (jwksType.trim().toLowerCase()) {
-                case "ec":
-                    KeyPairGenerator gen = KeyPairGenerator.getInstance("EC");
-                    KeyPair keyPair;
-                    switch (jwksKeySize) {
-                        case 384:
-                            gen.initialize(Curve.P_384.toECParameterSpec());
-                            keyPair = gen.generateKeyPair();
-                            jwk = new ECKey.Builder(Curve.P_384, (ECPublicKey) 
keyPair.getPublic()).
-                                    privateKey((ECPrivateKey) 
keyPair.getPrivate()).
-                                    keyUse(KeyUse.SIGNATURE).
-                                    keyID(jwksKeyId.concat("-").
-                                            
concat(SecureRandomUtils.generateRandomUUID().toString().substring(0, 8))).
-                                    build();
-                            break;
+    protected PublicJsonWebKey generate(
+            final String jwksKeyId,
+            final String jwksType,
+            final int jwksKeySize,
+            final String use,
+            final JsonWebKeyLifecycleState state) throws JoseException {
 
-                        case 512:
-                            gen.initialize(Curve.P_521.toECParameterSpec());
-                            keyPair = gen.generateKeyPair();
-                            jwk = new ECKey.Builder(Curve.P_521, (ECPublicKey) 
keyPair.getPublic()).
-                                    privateKey((ECPrivateKey) 
keyPair.getPrivate()).
-                                    keyUse(KeyUse.SIGNATURE).
-                                    keyID(jwksKeyId.concat("-").
-                                            
concat(SecureRandomUtils.generateRandomUUID().toString().substring(0, 8))).
-                                    build();
-                            break;
+        PublicJsonWebKey jwk;
+        switch (jwksType.trim().toLowerCase(Locale.ENGLISH)) {
+            case "ec":
+                switch (jwksKeySize) {
+                    case 384:
+                        jwk = EcJwkGenerator.generateJwk(EllipticCurves.P384);
+                        
jwk.setAlgorithm(AlgorithmIdentifiers.ECDSA_USING_P384_CURVE_AND_SHA384);
+                        break;
 
-                        default:
-                            gen.initialize(Curve.P_256.toECParameterSpec());
-                            keyPair = gen.generateKeyPair();
-                            jwk = new ECKey.Builder(Curve.P_256, (ECPublicKey) 
keyPair.getPublic()).
-                                    privateKey((ECPrivateKey) 
keyPair.getPrivate()).
-                                    keyUse(KeyUse.SIGNATURE).
-                                    keyID(jwksKeyId.concat("-").
-                                            
concat(SecureRandomUtils.generateRandomUUID().toString().substring(0, 8))).
-                                    build();
-                    }
-                    break;
+                    case 512:
+                        jwk = EcJwkGenerator.generateJwk(EllipticCurves.P521);
+                        
jwk.setAlgorithm(AlgorithmIdentifiers.ECDSA_USING_P521_CURVE_AND_SHA512);
+                        break;
+
+                    default:
+                        jwk = EcJwkGenerator.generateJwk(EllipticCurves.P256);
+                        
jwk.setAlgorithm(AlgorithmIdentifiers.ECDSA_USING_P521_CURVE_AND_SHA512);
+                }
+                break;
+
+            case "rsa":
+            default:
+                jwk = RsaJwkGenerator.generateJwk(jwksKeySize);
+        }
 
-                case "rsa":
-                default:
-                    jwk = new RSAKeyGenerator(jwksKeySize).
-                            keyUse(KeyUse.SIGNATURE).
-                            keyID(jwksKeyId.concat("-").
-                                    
concat(SecureRandomUtils.generateRandomUUID().toString().substring(0, 8))).
-                            generate();
-            }
-        } catch (JOSEException | InvalidAlgorithmParameterException | 
NoSuchAlgorithmException e) {
+        
jwk.setKeyId(jwksKeyId.concat("-").concat(SecureRandomUtils.generateRandomLetters(8)));
+        jwk.setUse(use);
+        jwk.setOtherParameter(PARAMETER_STATE, state.getState());
+        return jwk;
+    }
+
+    @Override
+    public OIDCJWKS create(final String jwksKeyId, final String jwksType, 
final int jwksKeySize) {
+        List<PublicJsonWebKey> keys = new ArrayList<>();
+        try {
+            keys.add(generate(jwksKeyId, jwksType, jwksKeySize, Use.SIGNATURE, 
JsonWebKeyLifecycleState.CURRENT));
+            keys.add(generate(jwksKeyId, jwksType, jwksKeySize, 
Use.ENCRYPTION, JsonWebKeyLifecycleState.CURRENT));
+            keys.add(generate(jwksKeyId, jwksType, jwksKeySize, Use.SIGNATURE, 
JsonWebKeyLifecycleState.FUTURE));
+            keys.add(generate(jwksKeyId, jwksType, jwksKeySize, 
Use.ENCRYPTION, JsonWebKeyLifecycleState.FUTURE));
+        } catch (JoseException e) {
             LOG.error("Could not create OIDC JWKS", e);
 
             SyncopeClientException sce = 
SyncopeClientException.build(ClientExceptionType.Unknown);
@@ -116,8 +112,8 @@ public class OIDCJWKSDataBinderImpl implements 
OIDCJWKSDataBinder {
             throw sce;
         }
 
-        OIDCJWKS jwks = entityFactory.newEntity(OIDCJWKS.class);
-        jwks.setJson(JSONObjectUtils.toJSONString(new 
JWKSet(jwk).toJSONObject(false)));
-        return jwks;
+        OIDCJWKS oidcJWKS = entityFactory.newEntity(OIDCJWKS.class);
+        oidcJWKS.setJson(new 
JsonWebKeySet(keys).toJson(JsonWebKey.OutputControlLevel.INCLUDE_PRIVATE));
+        return oidcJWKS;
     }
 }
diff --git a/docker/core/LICENSE b/docker/core/LICENSE
index 6d64d92476..733c2e0f64 100644
--- a/docker/core/LICENSE
+++ b/docker/core/LICENSE
@@ -1319,3 +1319,7 @@ This is licensed under the AL 2.0, see above.
 
 For SnakeYAML (http://www.snakeyaml.org/):
 This is licensed under the AL 2.0, see above.
+
+==
+For jose.4.j (https://bitbucket.org/b_c/jose4j/):
+This is licensed under the AL 2.0, see above.
diff --git a/pom.xml b/pom.xml
index ef3a22fce6..bce293734f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1298,6 +1298,12 @@ under the License.
         <version>${slf4j.version}</version>
       </dependency>
 
+      <dependency>
+        <groupId>org.bitbucket.b_c</groupId>
+        <artifactId>jose4j</artifactId>
+        <version>0.9.6</version>
+      </dependency>
+
       <dependency>
         <groupId>org.apache.pdfbox</groupId>
         <artifactId>pdfbox</artifactId>
diff --git 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java
index 0fec1e6d11..3a66274c86 100644
--- 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java
+++ 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java
@@ -105,6 +105,7 @@ import 
org.springframework.beans.factory.annotation.Qualifier;
 import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.context.ApplicationContext;
 import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -371,13 +372,15 @@ public class WAContext {
     @Bean
     public OidcJsonWebKeystoreGeneratorService 
oidcJsonWebKeystoreGeneratorService(
             final CasConfigurationProperties casProperties,
-            final WARestClient waRestClient) {
+            final WARestClient waRestClient,
+            final ApplicationContext applicationContext) {
 
         return new WAOIDCJWKSGeneratorService(
                 waRestClient,
                 
casProperties.getAuthn().getOidc().getJwks().getCore().getJwksKeyId(),
                 
casProperties.getAuthn().getOidc().getJwks().getCore().getJwksType(),
-                
casProperties.getAuthn().getOidc().getJwks().getCore().getJwksKeySize());
+                
casProperties.getAuthn().getOidc().getJwks().getCore().getJwksKeySize(),
+                applicationContext);
     }
 
     @Bean
diff --git 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/oidc/WAOIDCJWKSGeneratorService.java
 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/oidc/WAOIDCJWKSGeneratorService.java
index 04e36a99ec..053e37d541 100644
--- 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/oidc/WAOIDCJWKSGeneratorService.java
+++ 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/oidc/WAOIDCJWKSGeneratorService.java
@@ -26,11 +26,13 @@ import org.apache.syncope.common.lib.to.OIDCJWKSTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.rest.api.service.OIDCJWKSService;
 import org.apache.syncope.wa.bootstrap.WARestClient;
+import org.apereo.cas.oidc.jwks.generator.OidcJsonWebKeystoreGeneratedEvent;
 import org.apereo.cas.oidc.jwks.generator.OidcJsonWebKeystoreGeneratorService;
 import org.jose4j.jwk.JsonWebKey;
 import org.jose4j.jwk.JsonWebKeySet;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationContext;
 import org.springframework.core.io.ByteArrayResource;
 import org.springframework.core.io.Resource;
 
@@ -46,16 +48,20 @@ public class WAOIDCJWKSGeneratorService implements 
OidcJsonWebKeystoreGeneratorS
 
     protected final int jwksKeySize;
 
+    protected final ApplicationContext applicationContext;
+
     public WAOIDCJWKSGeneratorService(
             final WARestClient waRestClient,
             final String jwksKeyId,
             final String jwksType,
-            final int jwksKeySize) {
+            final int jwksKeySize,
+            final ApplicationContext applicationContext) {
 
         this.waRestClient = waRestClient;
         this.jwksKeyId = jwksKeyId;
         this.jwksType = jwksType;
         this.jwksKeySize = jwksKeySize;
+        this.applicationContext = applicationContext;
     }
 
     @Override
@@ -93,6 +99,9 @@ public class WAOIDCJWKSGeneratorService implements 
OidcJsonWebKeystoreGeneratorS
         if (jwksTO == null) {
             throw new IllegalStateException("Unable to determine OIDC JWKS 
resource");
         }
-        return new 
ByteArrayResource(jwksTO.getJson().getBytes(StandardCharsets.UTF_8), "OIDC 
JWKS");
+
+        Resource result = new 
ByteArrayResource(jwksTO.getJson().getBytes(StandardCharsets.UTF_8), "OIDC 
JWKS");
+        applicationContext.publishEvent(new 
OidcJsonWebKeystoreGeneratedEvent(this, result));
+        return result;
     }
 }


Reply via email to