Repository: gravitee-io/graviteeio-access-management
Pull request #303 (2018-08-28 08:19:31)

[Snyk] Fix for 3 vulnerable dependencies
#### Description
This PR fixes one or more vulnerable packages in the `maven` dependencies of this project.
See the [Snyk test report](https://app.snyk.io/org/gravitee.io/test/github/6a6fd711-c92d-4c8d-b407-f9ab18fe0031/master..snyk-fix-9tpxna) for more details.

#### Snyk Project: [gravitee-io/graviteeio-access-management:gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/pom.xml](https://app.snyk.io/org/gravitee.io/project/6a6fd711-c92d-4c8d-b407-f9ab18fe0031)
#### Snyk Organization: [gravitee.io](https://app.snyk.io/org/gravitee.io)

#### Changes included in this PR

- Changes to the following files to upgrade the vulnerable dependencies to a fixed version:
    - pom.xml


#### Vulnerabilities that will be fixed
##### With an upgrade:
  - `pom.xml`
      - `com.fasterxml.jackson.core:jackson-databind@2.9.3 > com.fasterxml.jackson.core:jackson-databind@2.9.5` 
          - [Deserialization of Untrusted Data](https://snyk.io/vuln/SNYK-JAVA-COMFASTERXMLJACKSONCORE-32111)
          - [Deserialization of Untrusted Data](https://snyk.io/vuln/SNYK-JAVA-COMFASTERXMLJACKSONCORE-32043)
          - [Deserialization of Untrusted Data](https://snyk.io/vuln/SNYK-JAVA-COMFASTERXMLJACKSONCORE-32044)



You can read more about Snyk's upgrade and patch logic in [Snyk's documentation](https://snyk.io/docs/using-snyk/).

Check the changes in this PR to ensure they won't cause issues with your project.

Stay secure,
The Snyk team

_**Note**: You are seeing this because you or someone else with access to this repository has authorised Snyk to open Fix PRs. To review the settings for this Snyk project please go to the [project settings page](https://app.snyk.io/org/gravitee.io/project/6a6fd711-c92d-4c8d-b407-f9ab18fe0031/settings)._

[//]: # (snyk:metadata:{"type":"auto","packageManager":"maven","vulns":["SNYK-JAVA-COMFASTERXMLJACKSONCORE-32043","SNYK-JAVA-COMFASTERXMLJACKSONCORE-32044","SNYK-JAVA-COMFASTERXMLJACKSONCORE-32111"],"patch":[],"upgrade":["SNYK-JAVA-COMFASTERXMLJACKSONCORE-32043","SNYK-JAVA-COMFASTERXMLJACKSONCORE-32044","SNYK-JAVA-COMFASTERXMLJACKSONCORE-32111"],"isBreakingChange":false,"env":"prod"})



Patch:
diff --git a/gravitee-am-certificate/gravitee-am-certificate-api/pom.xml b/gravitee-am-certificate/gravitee-am-certificate-api/pom.xml
index 9104b33535..08cedbf989 100644
--- a/gravitee-am-certificate/gravitee-am-certificate-api/pom.xml
+++ b/gravitee-am-certificate/gravitee-am-certificate-api/pom.xml
@@ -32,4 +32,20 @@
 
     <version>2.1.0-SNAPSHOT</version>
     <name>Gravitee IO - Access Management - Certificate - API</name>
+
+    <dependencies>
+        <!-- Gravitee.io dependencies -->
+        <dependency>
+            <groupId>io.gravitee.am.model</groupId>
+            <artifactId>gravitee-am-model</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <!-- reactive dependencies -->
+        <dependency>
+            <groupId>io.reactivex.rxjava2</groupId>
+            <artifactId>rxjava</artifactId>
+            <version>${rxjava.version}</version>
+        </dependency>
+    </dependencies>
 </project>
\ No newline at end of file
diff --git a/gravitee-am-certificate/gravitee-am-certificate-api/src/main/java/io/gravitee/am/certificate/api/CertificateMetadata.java b/gravitee-am-certificate/gravitee-am-certificate-api/src/main/java/io/gravitee/am/certificate/api/CertificateMetadata.java
new file mode 100644
index 0000000000..9104d71a46
--- /dev/null
+++ b/gravitee-am-certificate/gravitee-am-certificate-api/src/main/java/io/gravitee/am/certificate/api/CertificateMetadata.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.certificate.api;
+
+import java.util.Map;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class CertificateMetadata {
+
+    private Map<String, Object> metadata;
+
+    public Map<String, Object> getMetadata() {
+        return metadata;
+    }
+
+    public void setMetadata(Map<String, Object> metadata) {
+        this.metadata = metadata;
+    }
+}
diff --git a/gravitee-am-certificate/gravitee-am-certificate-api/src/main/java/io/gravitee/am/certificate/api/CertificateProvider.java b/gravitee-am-certificate/gravitee-am-certificate-api/src/main/java/io/gravitee/am/certificate/api/CertificateProvider.java
index 8766617dce..d6feb01966 100644
--- a/gravitee-am-certificate/gravitee-am-certificate-api/src/main/java/io/gravitee/am/certificate/api/CertificateProvider.java
+++ b/gravitee-am-certificate/gravitee-am-certificate-api/src/main/java/io/gravitee/am/certificate/api/CertificateProvider.java
@@ -15,13 +15,19 @@
  */
 package io.gravitee.am.certificate.api;
 
+import io.gravitee.am.model.jose.JWK;
+import io.reactivex.Flowable;
+import io.reactivex.Single;
+
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
  * @author GraviteeSource Team
  */
 public interface CertificateProvider {
 
-    String sign(String payload);
+    Single<String> sign(String payload);
+
+    Single<String> publicKey();
 
-    String publicKey();
+    Flowable<JWK> keys();
 }
diff --git a/gravitee-am-certificate/gravitee-am-certificate-javakeystore/pom.xml b/gravitee-am-certificate/gravitee-am-certificate-javakeystore/pom.xml
index 2415080da5..48c1caa273 100644
--- a/gravitee-am-certificate/gravitee-am-certificate-javakeystore/pom.xml
+++ b/gravitee-am-certificate/gravitee-am-certificate-javakeystore/pom.xml
@@ -32,6 +32,10 @@
     <version>2.1.0-SNAPSHOT</version>
     <name>Gravitee IO - Access Management - Certificate - JavaKeyStore</name>
 
+    <properties>
+        <nimbus.version>5.14</nimbus.version>
+    </properties>
+
     <dependencies>
         <!-- Gravitee.io -->
         <dependency>
@@ -48,12 +52,11 @@
             <scope>provided</scope>
         </dependency>
 
-        <!-- Jjwt -->
+        <!-- Nimbus jose+jwt -->
         <dependency>
-            <groupId>io.jsonwebtoken</groupId>
-            <artifactId>jjwt</artifactId>
-            <version>${jjwt.version}</version>
-            <scope>provided</scope>
+            <groupId>com.nimbusds</groupId>
+            <artifactId>nimbus-jose-jwt</artifactId>
+            <version>${nimbus.version}</version>
         </dependency>
     </dependencies>
 
diff --git a/gravitee-am-certificate/gravitee-am-certificate-javakeystore/src/main/java/io/gravitee/am/certificate/javakeystore/provider/JavaKeyStoreProvider.java b/gravitee-am-certificate/gravitee-am-certificate-javakeystore/src/main/java/io/gravitee/am/certificate/javakeystore/provider/JavaKeyStoreProvider.java
index d381abcb97..5f003ebe61 100644
--- a/gravitee-am-certificate/gravitee-am-certificate-javakeystore/src/main/java/io/gravitee/am/certificate/javakeystore/provider/JavaKeyStoreProvider.java
+++ b/gravitee-am-certificate/gravitee-am-certificate-javakeystore/src/main/java/io/gravitee/am/certificate/javakeystore/provider/JavaKeyStoreProvider.java
@@ -15,22 +15,34 @@
  */
 package io.gravitee.am.certificate.javakeystore.provider;
 
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSSigner;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import io.gravitee.am.certificate.api.CertificateMetadata;
 import io.gravitee.am.certificate.api.CertificateProvider;
 import io.gravitee.am.certificate.javakeystore.JavaKeyStoreConfiguration;
-import io.jsonwebtoken.Jwts;
-import io.jsonwebtoken.SignatureAlgorithm;
+import io.gravitee.am.model.jose.JWK;
+import io.gravitee.am.model.jose.RSAKey;
+import io.reactivex.Flowable;
+import io.reactivex.Single;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
 
-import java.io.ByteArrayOutputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.OutputStream;
+import java.io.*;
 import java.math.BigInteger;
 import java.security.*;
 import java.security.cert.Certificate;
 import java.security.interfaces.RSAPublicKey;
 import java.util.Base64;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
@@ -38,16 +50,32 @@ import java.util.Base64;
  */
 public class JavaKeyStoreProvider implements CertificateProvider, InitializingBean {
 
+    private Logger logger = LoggerFactory.getLogger(JavaKeyStoreProvider.class);
     private KeyPair keyPair;
+    private JWSSigner signer;
+    private JWKSet jwkSet;
+    private String publicKey;
+    private Set<JWK> keys;
 
     @Autowired
     private JavaKeyStoreConfiguration configuration;
 
+    @Autowired
+    private CertificateMetadata certificateMetadata;
+
     @Override
     public void afterPropertiesSet() throws Exception {
-        FileInputStream is = new FileInputStream(configuration.getJks());
+        Object file = certificateMetadata.getMetadata().get("file");
+        Objects.requireNonNull(file, "A jks file is required to use Java KeyStore certificate");
+
+        byte[] jksFile = (byte[]) file;
+        InputStream is = new ByteArrayInputStream(jksFile);
         KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
         keystore.load(is, configuration.getStorepass().toCharArray());
+        // generate JWK set
+        jwkSet = JWKSet.load(keystore, name -> configuration.getKeypass().toCharArray());
+        keys = getKeys();
+        // generate Key pair
         Key key = keystore.getKey(configuration.getAlias(), configuration.getKeypass().toCharArray());
         if (key instanceof PrivateKey) {
             // Get certificate of public key
@@ -56,37 +84,66 @@ public class JavaKeyStoreProvider implements CertificateProvider, InitializingBe
             PublicKey publicKey = cert.getPublicKey();
             // Return a key pair
             keyPair = new KeyPair(publicKey, (PrivateKey) key);
+            // Create RSA-signer with the private key
+            signer = new RSASSASigner(keyPair.getPrivate());
+            // get public key
+            this.publicKey =  getPublicKey();
         } else {
             throw new IllegalArgumentException("A RSA Signer must be supplied");
         }
     }
 
     @Override
-    public String sign(String payload) {
-        return Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.RS512, keyPair.getPrivate()).compact();
+    public Single<String> sign(String payload) {
+        return Single.create(emitter -> {
+            try {
+                JWTClaimsSet claimsSet = JWTClaimsSet.parse(payload);
+                JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS512).keyID(configuration.getAlias()).build();
+
+                SignedJWT signedJWT = new SignedJWT(header, claimsSet);
+
+                // compute the RSA signature
+                signedJWT.sign(signer);
+
+                // return signed JWT
+                emitter.onSuccess(signedJWT.serialize());
+            } catch (Exception e) {
+                logger.error("An error occurs while signing JWT token payload", e);
+                emitter.onError(e);
+            }
+        });
     }
 
     @Override
-    public String publicKey() {
+    public Single<String> publicKey() {
+        return Single.just(publicKey);
+    }
+
+    @Override
+    public Flowable<JWK> keys() {
+        return Flowable.fromIterable(keys);
+    }
+
+    private String getPublicKey() throws IOException {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
-       /* encode the "ssh-rsa" string */
-        try {
-            byte[] sshrsa = new byte[] {0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a'};
-            out.write(sshrsa);
-            /* Encode the public exponent */
-            BigInteger e = ((RSAPublicKey) keyPair.getPublic()).getPublicExponent();
-            byte[] data = e.toByteArray();
-            encodeUInt32(data.length, out);
-            out.write(data);
-            /* Encode the modulus */
-            BigInteger m = ((RSAPublicKey) keyPair.getPublic()).getModulus();
-            data = m.toByteArray();
-            encodeUInt32(data.length, out);
-            out.write(data);
-            return Base64.getEncoder().encodeToString(out.toByteArray());
-        } catch (Exception e) {
-            return null;
-        }
+        /* encode the "ssh-rsa" string */
+        byte[] sshrsa = new byte[]{0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a'};
+        out.write(sshrsa);
+        /* Encode the public exponent */
+        BigInteger e = ((RSAPublicKey) keyPair.getPublic()).getPublicExponent();
+        byte[] data = e.toByteArray();
+        encodeUInt32(data.length, out);
+        out.write(data);
+        /* Encode the modulus */
+        BigInteger m = ((RSAPublicKey) keyPair.getPublic()).getModulus();
+        data = m.toByteArray();
+        encodeUInt32(data.length, out);
+        out.write(data);
+        return Base64.getEncoder().encodeToString(out.toByteArray());
+    }
+
+    private Set<JWK> getKeys() {
+        return jwkSet.toPublicJWKSet().getKeys().stream().map(this::convert).collect(Collectors.toSet());
     }
 
     private void encodeUInt32(int value, OutputStream out) throws IOException {
@@ -98,5 +155,47 @@ public class JavaKeyStoreProvider implements CertificateProvider, InitializingBe
         out.write(tmp);
     }
 
+    private JWK convert(com.nimbusds.jose.jwk.JWK nimbusJwk) {
+        RSAKey jwk = new RSAKey();
+        if (nimbusJwk.getKeyType() != null) {
+            jwk.setKty(nimbusJwk.getKeyType().getValue());
+        }
+        if (nimbusJwk.getKeyUse() != null) {
+            jwk.setUse(nimbusJwk.getKeyUse().identifier());
+        }
+        if (nimbusJwk.getKeyOperations() != null) {
+            jwk.setKeyOps(nimbusJwk.getKeyOperations().stream().map(keyOperation -> keyOperation.identifier()).collect(Collectors.toSet()));
+        }
+        if (nimbusJwk.getAlgorithm() != null) {
+            jwk.setAlg(nimbusJwk.getAlgorithm().getName());
+        }
+        if (nimbusJwk.getKeyID() != null) {
+            jwk.setKid(nimbusJwk.getKeyID());
+        }
+        if (nimbusJwk.getX509CertURL() != null) {
+            jwk.setX5u(nimbusJwk.getX509CertURL().toString());
+        }
+        if (nimbusJwk.getX509CertChain() != null) {
+            jwk.setX5c(nimbusJwk.getX509CertChain().stream().map(cert -> cert.toString()).collect(Collectors.toSet()));
+        }
+        if (nimbusJwk.getX509CertThumbprint() != null) {
+            jwk.setX5t(nimbusJwk.getX509CertThumbprint().toString());
+        }
+        if (nimbusJwk.getX509CertSHA256Thumbprint() != null) {
+            jwk.setX5tS256(nimbusJwk.getX509CertSHA256Thumbprint().toString());
+        }
+
+        // specific RSA Key
+        com.nimbusds.jose.jwk.RSAKey nimbusRSAJwk = (com.nimbusds.jose.jwk.RSAKey) nimbusJwk;
+        if (nimbusRSAJwk.getPublicExponent() != null) {
+            jwk.setE(nimbusRSAJwk.getPublicExponent().toString());
+        }
+        if (nimbusRSAJwk.getModulus() != null) {
+            jwk.setN(nimbusRSAJwk.getModulus().toString());
+        }
+
+        return jwk;
+    }
+
 
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/certificate/CertificateManager.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/certificate/CertificateManager.java
index cfbe539f96..7c3b286935 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/certificate/CertificateManager.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/certificate/CertificateManager.java
@@ -18,6 +18,8 @@ package io.gravitee.am.gateway.handler.oauth2.certificate;
 import io.gravitee.am.certificate.api.CertificateProvider;
 import io.reactivex.Maybe;
 
+import java.util.Collection;
+
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
  * @author GraviteeSource Team
@@ -25,4 +27,6 @@ import io.reactivex.Maybe;
 public interface CertificateManager {
 
     Maybe<CertificateProvider> get(String id);
+
+    Collection<CertificateProvider> providers();
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/certificate/impl/CertificateManagerImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/certificate/impl/CertificateManagerImpl.java
index 738a02f0b8..639650c331 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/certificate/impl/CertificateManagerImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/certificate/impl/CertificateManagerImpl.java
@@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
 
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -54,6 +55,11 @@ public class CertificateManagerImpl implements CertificateManager, InitializingB
         return (certificateProvider != null) ? Maybe.just(certificateProvider) : Maybe.empty();
     }
 
+    @Override
+    public Collection<CertificateProvider> providers() {
+        return certificateProviders.values();
+    }
+
     @Override
     public void afterPropertiesSet() {
         logger.info("Initializing certificates for domain {}", domain.getName());
@@ -64,7 +70,7 @@ public class CertificateManagerImpl implements CertificateManager, InitializingB
                         logger.info("\tInitializing certificate: {} [{}]", certificate.getName(), certificate.getType());
 
                         CertificateProvider certificateProvider =
-                                certificatePluginManager.create(certificate.getType(), certificate.getConfiguration());
+                                certificatePluginManager.create(certificate.getType(), certificate.getConfiguration(), certificate.getMetadata());
                         certificateProviders.put(certificate.getId(), certificateProvider);
                     });
                     logger.info("Certificates loaded for domain {}", domain.getName());
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/code/impl/AuthorizationCodeServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/code/impl/AuthorizationCodeServiceImpl.java
index 6d6fef6a81..9341e451c8 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/code/impl/AuthorizationCodeServiceImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/code/impl/AuthorizationCodeServiceImpl.java
@@ -49,6 +49,7 @@ public class AuthorizationCodeServiceImpl implements AuthorizationCodeService {
         authorizationCode.setClientId(authorizationRequest.getClientId());
         authorizationCode.setSubject(user.getId());
         authorizationCode.setScopes(authorizationRequest.getScopes());
+        authorizationCode.setRequestParameters(authorizationRequest.getRequestParameters());
         authorizationCode.setExpireAt(new Date(System.currentTimeMillis() + authorizationCodeValidity));
         authorizationCode.setCreatedAt(new Date());
 
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/granter/code/AuthorizationCodeTokenGranter.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/granter/code/AuthorizationCodeTokenGranter.java
index 471891cd96..96e15bead0 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/granter/code/AuthorizationCodeTokenGranter.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/granter/code/AuthorizationCodeTokenGranter.java
@@ -83,6 +83,10 @@ public class AuthorizationCodeTokenGranter extends AbstractTokenGranter {
                     return super.createOAuth2Request(tokenRequest, client)
                             .map(oAuth2Request -> {
                                 oAuth2Request.setSubject(authorizationCode.getSubject());
+                                // set authorization code initial request parameters (step1 of authorization code flow)
+                                if (authorizationCode.getRequestParameters() != null) {
+                                    authorizationCode.getRequestParameters().forEach((key, value) -> oAuth2Request.getRequestParameters().putIfAbsent(key, value));
+                                }
                                 return oAuth2Request;
                             });
                 });
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/token/impl/TokenEnhancerImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/token/impl/TokenEnhancerImpl.java
index cad35b0d0c..ef6129f55c 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/token/impl/TokenEnhancerImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oauth2/token/impl/TokenEnhancerImpl.java
@@ -16,6 +16,7 @@
 package io.gravitee.am.gateway.handler.oauth2.token.impl;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import io.gravitee.am.certificate.api.CertificateProvider;
 import io.gravitee.am.gateway.handler.oauth2.certificate.CertificateManager;
 import io.gravitee.am.gateway.handler.oauth2.client.ClientService;
 import io.gravitee.am.gateway.handler.oauth2.request.OAuth2Request;
@@ -26,13 +27,16 @@ import io.gravitee.am.gateway.service.RoleService;
 import io.gravitee.am.gateway.service.UserService;
 import io.gravitee.am.model.Client;
 import io.gravitee.am.model.User;
+import io.gravitee.am.model.jose.JWK;
 import io.gravitee.am.repository.oauth2.model.AccessToken;
 import io.gravitee.am.service.exception.ClientNotFoundException;
 import io.gravitee.am.service.exception.UserNotFoundException;
+import io.jsonwebtoken.JwsHeader;
 import io.jsonwebtoken.JwtBuilder;
 import io.jsonwebtoken.Jwts;
 import io.jsonwebtoken.SignatureAlgorithm;
 import io.jsonwebtoken.impl.crypto.MacProvider;
+import io.reactivex.Flowable;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
 import io.reactivex.SingleSource;
@@ -55,7 +59,7 @@ public class TokenEnhancerImpl implements TokenEnhancer, InitializingBean {
     private static final String OPEN_ID = "openid";
     private static final String ID_TOKEN = "id_token";
     private ObjectMapper objectMapper = new ObjectMapper();
-    private JwtBuilder jwtBuilder;
+    private CertificateProvider defaultCertificateProvider;
 
     @Value("${oidc.iss:http://gravitee.am}")
     private String iss;
@@ -63,6 +67,9 @@ public class TokenEnhancerImpl implements TokenEnhancer, InitializingBean {
     @Value("${oidc.signing.key.secret:s3cR3t4grAv1t33}")
     private String signingKeySecret;
 
+    @Value("${oidc.signing.key.kid:default-gravitee-AM-key}")
+    private String signingKeyId;
+
     @Autowired
     private ClientService clientService;
 
@@ -79,7 +86,10 @@ public class TokenEnhancerImpl implements TokenEnhancer, InitializingBean {
     public void afterPropertiesSet() {
         // create default signing HMAC key
         Key key = MacProvider.generateKey(SignatureAlgorithm.HS512, new SecureRandom(signingKeySecret.getBytes()));
-        jwtBuilder = Jwts.builder().signWith(SignatureAlgorithm.HS512, key);
+        JwtBuilder jwtBuilder = Jwts.builder().signWith(SignatureAlgorithm.HS512, key);
+
+        // create default certificate provider
+        setDefaultCertificateProvider(jwtBuilder);
     }
 
     @Override
@@ -161,6 +171,12 @@ public class TokenEnhancerImpl implements TokenEnhancer, InitializingBean {
         calendar.add(Calendar.SECOND, client.getIdTokenValiditySeconds() > 0 ? client.getIdTokenValiditySeconds() : defaultIDTokenExpireIn);
         IDToken.put(OIDCClaims.exp, calendar.getTimeInMillis() / 1000l);
 
+        // set nonce
+        String nonce = oAuth2Request.getRequestParameters().getFirst(OIDCClaims.nonce);
+        if (nonce != null && !nonce.isEmpty()) {
+            IDToken.put(OIDCClaims.nonce, nonce);
+        }
+
         // override claims for an end-user
         if (!oAuth2Request.isClientOnly() && client.getIdTokenCustomClaims() != null) {
             if (user.getAdditionalInformation() != null && !user.getAdditionalInformation().isEmpty()) {
@@ -175,9 +191,9 @@ public class TokenEnhancerImpl implements TokenEnhancer, InitializingBean {
 
         // sign the ID Token and add id_token field to the access_token
         return certificateManager.get(client.getCertificate())
-                .map(certificateProvider -> certificateProvider.sign(objectMapper.writeValueAsString(IDToken)))
-                .defaultIfEmpty(jwtBuilder.setClaims(IDToken).compact())
-                .flatMapSingle(payload -> {
+                .defaultIfEmpty(defaultCertificateProvider)
+                .flatMapSingle(certificateProvider ->  certificateProvider.sign(objectMapper.writeValueAsString(IDToken)))
+                .flatMap(payload -> {
                     Map<String, Object> additionalInformation = new HashMap<>(accessToken.getAdditionalInformation());
                     additionalInformation.put(ID_TOKEN, payload);
                     accessToken.setAdditionalInformation(additionalInformation);
@@ -186,7 +202,26 @@ public class TokenEnhancerImpl implements TokenEnhancer, InitializingBean {
     }
 
     public void setJwtBuilder(JwtBuilder jwtBuilder) {
-        this.jwtBuilder = jwtBuilder;
+        setDefaultCertificateProvider(jwtBuilder);
+    }
+
+    private void setDefaultCertificateProvider(JwtBuilder jwtBuilder) {
+        defaultCertificateProvider = new CertificateProvider() {
+            @Override
+            public Single<String> sign(String payload) {
+                return Single.just(jwtBuilder.setPayload(payload).compact());
+            }
+
+            @Override
+            public Single<String> publicKey() {
+                return null;
+            }
+
+            @Override
+            public Flowable<JWK> keys() {
+                return null;
+            }
+        };
     }
 
     private class TokenEnhancerData {
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/jwk/JWK.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/jwk/JWK.java
new file mode 100644
index 0000000000..d01e0e8805
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/jwk/JWK.java
@@ -0,0 +1,157 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.gateway.handler.oidc.jwk;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Set;
+
+/**
+ * See <a href="https://tools.ietf.org/html/rfc7517#section-4">4. JSON Web Key (JWK) Format</a>
+ *
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public abstract class JWK {
+
+    /**
+     * "kty" (Key Type) Parameter
+     */
+    @JsonProperty("kty")
+    private String kty;
+
+    /**
+     * "use" (Public Key Use) Parameter
+     */
+    @JsonProperty("use")
+    private String use;
+
+    /**
+     * "key_ops" (Key Operations) Parameter
+     */
+    @JsonProperty("key_ops")
+    private Set<String> keyOps;
+
+    /**
+     * "alg" (Algorithm) Parameter
+     */
+    @JsonProperty("alg")
+    private String alg;
+
+    /**
+     * "kid" (Key ID) Parameter
+     */
+    @JsonProperty("kid")
+    private String kid;
+
+    /**
+     * "x5u" (X.509 URL) Parameter
+     */
+    @JsonProperty("x5u")
+    private String x5u;
+
+    /**
+     * "x5c" (X.509 Certificate Chain) Parameter
+     */
+    @JsonProperty("x5c")
+    private Set<String> x5c;
+
+    /**
+     * "x5t" (X.509 Certificate SHA-1 Thumbprint) Parameter
+     */
+    @JsonProperty("x5t")
+    private String x5t;
+
+    /**
+     * "x5t#S256" (X.509 Certificate SHA-256 Thumbprint) Parameter
+     */
+    @JsonProperty("x5t#S256")
+    private String x5tS256;
+
+    public String getKty() {
+        return kty;
+    }
+
+    public void setKty(String kty) {
+        this.kty = kty;
+    }
+
+    public String getUse() {
+        return use;
+    }
+
+    public void setUse(String use) {
+        this.use = use;
+    }
+
+    public Set<String> getKeyOps() {
+        return keyOps;
+    }
+
+    public void setKeyOps(Set<String> keyOps) {
+        this.keyOps = keyOps;
+    }
+
+    public String getAlg() {
+        return alg;
+    }
+
+    public void setAlg(String alg) {
+        this.alg = alg;
+    }
+
+    public String getKid() {
+        return kid;
+    }
+
+    public void setKid(String kid) {
+        this.kid = kid;
+    }
+
+    public String getX5u() {
+        return x5u;
+    }
+
+    public void setX5u(String x5u) {
+        this.x5u = x5u;
+    }
+
+    public Set<String> getX5c() {
+        return x5c;
+    }
+
+    public void setX5c(Set<String> x5c) {
+        this.x5c = x5c;
+    }
+
+    public String getX5t() {
+        return x5t;
+    }
+
+    public void setX5t(String x5t) {
+        this.x5t = x5t;
+    }
+
+    public String getX5tS256() {
+        return x5tS256;
+    }
+
+    public void setX5tS256(String x5tS256) {
+        this.x5tS256 = x5tS256;
+    }
+}
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/jwk/JWKSet.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/jwk/JWKSet.java
new file mode 100644
index 0000000000..efbd4fc4de
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/jwk/JWKSet.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.gateway.handler.oidc.jwk;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+/**
+ * See <a href="https://tools.ietf.org/html/rfc7517#section-5">5. JWK Set Format</a>
+ *
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class JWKSet {
+
+    @JsonProperty("keys")
+    private List<JWK> keys;
+
+    public List<JWK> getKeys() {
+        return keys;
+    }
+
+    public void setKeys(List<JWK> keys) {
+        this.keys = keys;
+    }
+}
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/jwk/JWKSetService.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/jwk/JWKSetService.java
new file mode 100644
index 0000000000..b0848f2b05
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/jwk/JWKSetService.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.gateway.handler.oidc.jwk;
+
+import io.reactivex.Single;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public interface JWKSetService {
+
+    Single<JWKSet> getKeys();
+}
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/jwk/RSAKey.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/jwk/RSAKey.java
new file mode 100644
index 0000000000..29a29c341a
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/jwk/RSAKey.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.gateway.handler.oidc.jwk;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * See <a href="https://tools.ietf.org/html/rfc7638#section-3.2">3.2. JWK Members Used in the Thumbprint Computation</a>
+ *
+ *  The required members for an RSA public key, in lexicographic order, are:
+ *    - "e"
+ *    - "kty"
+ *    - "n"
+ * @author Titouan COMPIEGNE (titouan.compiegne@graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class RSAKey extends JWK {
+
+    /**
+     * The public exponent of the RSA key.
+     */
+    @JsonProperty("e")
+    private String e;
+
+    /**
+     * The modulus value for the RSA key.
+     */
+    @JsonProperty("n")
+    private String n;
+
+    public String getE() {
+        return e;
+    }
+
+    public void setE(String e) {
+        this.e = e;
+    }
+
+    public String getN() {
+        return n;
+    }
+
+    public void setN(String n) {
+        this.n = n;
+    }
+}
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/jwk/impl/JWKSetServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/jwk/impl/JWKSetServiceImpl.java
new file mode 100644
index 0000000000..0e1ef89bca
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/jwk/impl/JWKSetServiceImpl.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.gateway.handler.oidc.jwk.impl;
+
+import io.gravitee.am.gateway.handler.oauth2.certificate.CertificateManager;
+import io.gravitee.am.gateway.handler.oidc.jwk.JWK;
+import io.gravitee.am.gateway.handler.oidc.jwk.JWKSet;
+import io.gravitee.am.gateway.handler.oidc.jwk.JWKSetService;
+import io.gravitee.am.gateway.handler.oidc.jwk.RSAKey;
+import io.reactivex.Flowable;
+import io.reactivex.Single;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class JWKSetServiceImpl implements JWKSetService {
+
+    @Autowired
+    private CertificateManager certificateManager;
+
+    @Override
+    public Single<JWKSet> getKeys() {
+        return Flowable.fromIterable(certificateManager.providers())
+                .flatMap(certificateProvider -> certificateProvider.keys())
+                .map(this::convert)
+                .toList()
+                .map(keys -> {
+                    JWKSet jwkSet = new JWKSet();
+                    jwkSet.setKeys(keys);
+                    return jwkSet;
+                });
+
+    }
+
+    private JWK convert(io.gravitee.am.model.jose.JWK jwk) {
+        JWK jwk1 = new RSAKey();
+        jwk1.setKty(jwk.getKty());
+        jwk1.setUse(jwk.getUse());
+        jwk1.setKeyOps(jwk.getKeyOps());
+        jwk1.setAlg(jwk.getAlg());
+        jwk1.setKid(jwk.getKid());
+        jwk1.setX5u(jwk.getX5u());
+        jwk1.setX5c(jwk.getX5c());
+        jwk1.setX5t(jwk.getX5t());
+        jwk1.setX5tS256(jwk.getX5tS256());
+
+        // specific RSA Key
+        ((RSAKey) jwk1).setE(((io.gravitee.am.model.jose.RSAKey)jwk).getE());
+        ((RSAKey) jwk1).setN(((io.gravitee.am.model.jose.RSAKey)jwk).getN());
+
+        return jwk1;
+    }
+}
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/spring/OpenIDConfiguration.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/spring/OpenIDConfiguration.java
index 4d937d2529..637120a3e7 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/spring/OpenIDConfiguration.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/oidc/spring/OpenIDConfiguration.java
@@ -17,6 +17,8 @@ package io.gravitee.am.gateway.handler.oidc.spring;
 
 import io.gravitee.am.gateway.handler.oidc.discovery.OpenIDDiscoveryService;
 import io.gravitee.am.gateway.handler.oidc.discovery.impl.OpenIDDiscoveryServiceImpl;
+import io.gravitee.am.gateway.handler.oidc.jwk.JWKSetService;
+import io.gravitee.am.gateway.handler.oidc.jwk.impl.JWKSetServiceImpl;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
@@ -31,4 +33,9 @@ public class OpenIDConfiguration {
     public OpenIDDiscoveryService openIDConfigurationService() {
         return new OpenIDDiscoveryServiceImpl();
     }
+
+    @Bean
+    public JWKSetService jwkSetService() {
+        return new JWKSetServiceImpl();
+    }
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oidc/OIDCRouter.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oidc/OIDCRouter.java
index 887ad70b21..367ffe1165 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oidc/OIDCRouter.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oidc/OIDCRouter.java
@@ -17,7 +17,9 @@ package io.gravitee.am.gateway.handler.vertx.handler.oidc;
 
 import io.gravitee.am.gateway.handler.oauth2.token.TokenService;
 import io.gravitee.am.gateway.handler.oidc.discovery.OpenIDDiscoveryService;
+import io.gravitee.am.gateway.handler.oidc.jwk.JWKSetService;
 import io.gravitee.am.gateway.handler.vertx.handler.oidc.endpoint.ProviderConfigurationEndpoint;
+import io.gravitee.am.gateway.handler.vertx.handler.oidc.endpoint.ProviderJWKSetEndpoint;
 import io.gravitee.am.gateway.handler.vertx.handler.oidc.endpoint.UserInfoEndpoint;
 import io.gravitee.am.gateway.handler.vertx.handler.oidc.handler.UserInfoRequestParseHandler;
 import io.gravitee.am.gateway.service.UserService;
@@ -46,6 +48,9 @@ public class OIDCRouter {
     @Autowired
     private OpenIDDiscoveryService discoveryService;
 
+    @Autowired
+    private JWKSetService jwkSetService;
+
     @Autowired
     private TokenService tokenService;
 
@@ -82,6 +87,13 @@ public class OIDCRouter {
                 .handler(userInfoRequestParseHandler)
                 .handler(userInfoEndpoint);
 
+        // OpenID Provider JWK Set
+        Handler<RoutingContext> openIDProviderJWKSetEndpoint = new ProviderJWKSetEndpoint();
+        ((ProviderJWKSetEndpoint) openIDProviderJWKSetEndpoint).setJwkSetService(jwkSetService);
+        router
+                .route(HttpMethod.GET, "/.well-known/jwks.json")
+                .handler(openIDProviderJWKSetEndpoint);
+
         return router;
     }
 
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oidc/endpoint/ProviderJWKSetEndpoint.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oidc/endpoint/ProviderJWKSetEndpoint.java
new file mode 100644
index 0000000000..4b605d7647
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/main/java/io/gravitee/am/gateway/handler/vertx/handler/oidc/endpoint/ProviderJWKSetEndpoint.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.gateway.handler.vertx.handler.oidc.endpoint;
+
+import io.gravitee.am.gateway.handler.oidc.jwk.JWKSetService;
+import io.gravitee.common.http.HttpHeaders;
+import io.vertx.core.Handler;
+import io.vertx.core.json.Json;
+import io.vertx.reactivex.ext.web.RoutingContext;
+
+/**
+ * The JWKSet Endpoint provide a set of JWKs (keys) to enable clients to verify the authenticity of JWT tokens.
+ *
+ * A JWK Set is a JSON object that represents a set of JWKs.
+ * The JSON object MUST have a "keys" member, with its value being an array ofxJWKs.
+ * This JSON object MAY contain whitespace and/or line breaks.
+ *
+ * See <a href="https://tools.ietf.org/html/rfc7517#section-5">5. JWK Set Format</a>
+ *
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class ProviderJWKSetEndpoint implements Handler<RoutingContext> {
+
+    private JWKSetService jwkSetService;
+
+    @Override
+    public void handle(RoutingContext context) {
+        jwkSetService.getKeys().subscribe(keys -> context.response()
+                .putHeader(HttpHeaders.CACHE_CONTROL, "no-store")
+                .putHeader(HttpHeaders.PRAGMA, "no-cache")
+                .putHeader(HttpHeaders.CONTENT_TYPE, "application/jwk-set+json; charset=UTF-8")
+                .end(Json.encodePrettily(keys)), error -> context.response().setStatusCode(500).end());
+    }
+
+    public void setJwkSetService(JWKSetService jwkSetService) {
+        this.jwkSetService = jwkSetService;
+    }
+}
diff --git a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/certificate/CertificateManager.java b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/certificate/CertificateManager.java
index dedce3ad2e..d1c2d3a418 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/certificate/CertificateManager.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/certificate/CertificateManager.java
@@ -44,7 +44,7 @@ public class CertificateManager implements InitializingBean  {
     private CertificateService certificateService;
 
     public void reloadCertificateProviders(Certificate certificate) {
-        CertificateProvider certificateProvider = certificatePluginManager.create(certificate.getType(), certificate.getConfiguration());
+        CertificateProvider certificateProvider = certificatePluginManager.create(certificate.getType(), certificate.getConfiguration(), certificate.getMetadata());
         certificateService.setCertificateProvider(certificate.getId(), certificateProvider);
     }
 
@@ -57,7 +57,7 @@ public class CertificateManager implements InitializingBean  {
             LOGGER.info("\tInitializing certificate: {} [{}]", certificate.getName(), certificate.getType());
 
             CertificateProvider certificateProvider =
-                    certificatePluginManager.create(certificate.getType(), certificate.getConfiguration());
+                    certificatePluginManager.create(certificate.getType(), certificate.getConfiguration(), certificate.getMetadata());
             certificateProviders.put(certificate.getId(), certificateProvider);
         });
 
diff --git a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/resources/CertificateResource.java b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/resources/CertificateResource.java
index 07d46d67af..5a4ed86433 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/resources/CertificateResource.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/resources/CertificateResource.java
@@ -98,8 +98,9 @@ public class CertificateResource {
                              @PathParam("certificate") String certificate,
                              @Suspended final AsyncResponse response) {
         certificateService.getCertificateProvider(certificate)
-                .map(certificateProvider -> Response.ok(certificateProvider.publicKey()).build())
                 .switchIfEmpty(Maybe.error(new BadRequestException("No certificate provider found for the certificate " + certificate)))
+                .flatMapSingle(certificateProvider -> certificateProvider.publicKey())
+                .map(publicKey -> Response.ok(publicKey).build())
                 .subscribe(
                         result -> response.resume(result),
                         error -> response.resume(error));
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/upgrades/CertificateUpgrader.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/upgrades/CertificateUpgrader.java
new file mode 100644
index 0000000000..8992906f8f
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/upgrades/CertificateUpgrader.java
@@ -0,0 +1,89 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.management.service.impl.upgrades;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.gravitee.am.model.Certificate;
+import io.gravitee.am.service.CertificateService;
+import io.reactivex.Observable;
+import io.reactivex.Single;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.Ordered;
+import org.springframework.stereotype.Component;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Component
+public class CertificateUpgrader implements Upgrader, Ordered {
+
+    private static final Logger logger = LoggerFactory.getLogger(CertificateUpgrader.class);
+    private static final String JAVA_KEYSTORE_AM_TYPE = "javakeystore-am-certificate";
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    @Autowired
+    private CertificateService certificateService;
+
+    @Override
+    public boolean upgrade() {
+        logger.info("Applying certificate upgrade");
+        certificateService.findAll()
+                .flatMapObservable(certificates -> Observable.fromIterable(certificates))
+                // only AM JAVA KEYSTORE must be update if the jks file is still set in the filesystem
+                .filter(certificate -> JAVA_KEYSTORE_AM_TYPE.equals(certificate.getType()))
+                .flatMapSingle(certificate -> upgradeCertificate(certificate))
+                .subscribe();
+        return true;
+    }
+
+    private Single<Certificate> upgradeCertificate(Certificate certificate)  {
+        if (certificate.getMetadata() == null || certificate.getMetadata().get("file") == null) {
+            logger.info("Update certificate " + certificate.getName() + ", trying to upload the jks file to the repository ...");
+            try {
+                JsonNode configuration = objectMapper.readTree(certificate.getConfiguration());
+                String jksFilePath = configuration.get("jks").asText();
+                File file = new File(jksFilePath);
+                byte[] fileContent = Files.readAllBytes(file.toPath());
+                // update metadata
+                Map<String, Object> metadata = (certificate.getMetadata() == null) ? new HashMap<>() : new HashMap<>(certificate.getMetadata());
+                metadata.put("file", fileContent);
+                certificate.setMetadata(metadata);
+                return certificateService.update(certificate)
+                        .doOnSuccess(certificate1 -> logger.info("Certificate " + certificate1.getName() + " has been updated"));
+            } catch (Exception e) {
+                logger.error("An error occurs during certificate update process", e);
+                return Single.error(e);
+            }
+        } else {
+            // nothing to do
+            return Single.just(certificate);
+        }
+    }
+
+    @Override
+    public int getOrder() {
+        return 161;
+    }
+}
diff --git a/gravitee-am-model/src/main/java/io/gravitee/am/model/Certificate.java b/gravitee-am-model/src/main/java/io/gravitee/am/model/Certificate.java
index f741d99547..36bf146ac9 100644
--- a/gravitee-am-model/src/main/java/io/gravitee/am/model/Certificate.java
+++ b/gravitee-am-model/src/main/java/io/gravitee/am/model/Certificate.java
@@ -16,6 +16,7 @@
 package io.gravitee.am.model;
 
 import java.util.Date;
+import java.util.Map;
 
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
@@ -33,6 +34,8 @@ public class Certificate {
 
     private String domain;
 
+    private Map<String, Object> metadata;
+
     private Date createdAt;
 
     private Date updatedAt;
@@ -77,6 +80,14 @@ public class Certificate {
         this.domain = domain;
     }
 
+    public Map<String, Object> getMetadata() {
+        return metadata;
+    }
+
+    public void setMetadata(Map<String, Object> metadata) {
+        this.metadata = metadata;
+    }
+
     public Date getCreatedAt() {
         return createdAt;
     }
diff --git a/gravitee-am-model/src/main/java/io/gravitee/am/model/jose/JWK.java b/gravitee-am-model/src/main/java/io/gravitee/am/model/jose/JWK.java
new file mode 100644
index 0000000000..dc9c2a85cd
--- /dev/null
+++ b/gravitee-am-model/src/main/java/io/gravitee/am/model/jose/JWK.java
@@ -0,0 +1,144 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.model.jose;
+
+import java.util.Set;
+
+/**
+ * See <a href="https://tools.ietf.org/html/rfc7517#section-4">JSON Web Key (JWK)</a>
+ *
+ * @author Titouan COMPIEGNE (titouan.compiegne@graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public abstract class JWK {
+
+    /**
+     * "kty" (Key Type) Parameter
+     */
+    private String kty;
+
+    /**
+     * "use" (Public Key Use) Parameter
+     */
+    private String use;
+
+    /**
+     * "key_ops" (Key Operations) Parameter
+     */
+    private Set<String> keyOps;
+
+    /**
+     * "alg" (Algorithm) Parameter
+     */
+    private String alg;
+
+    /**
+     * "kid" (Key ID) Parameter
+     */
+    private String kid;
+
+    /**
+     * "x5u" (X.509 URL) Parameter
+     */
+    private String x5u;
+
+    /**
+     * "x5c" (X.509 Certificate Chain) Parameter
+     */
+    private Set<String> x5c;
+
+    /**
+     * "x5t" (X.509 Certificate SHA-1 Thumbprint) Parameter
+     */
+    private String x5t;
+
+    /**
+     * "x5t#S256" (X.509 Certificate SHA-256 Thumbprint) Parameter
+     */
+    private String x5tS256;
+
+    public String getKty() {
+        return kty;
+    }
+
+    public void setKty(String kty) {
+        this.kty = kty;
+    }
+
+    public String getUse() {
+        return use;
+    }
+
+    public void setUse(String use) {
+        this.use = use;
+    }
+
+    public Set<String> getKeyOps() {
+        return keyOps;
+    }
+
+    public void setKeyOps(Set<String> keyOps) {
+        this.keyOps = keyOps;
+    }
+
+    public String getAlg() {
+        return alg;
+    }
+
+    public void setAlg(String alg) {
+        this.alg = alg;
+    }
+
+    public String getKid() {
+        return kid;
+    }
+
+    public void setKid(String kid) {
+        this.kid = kid;
+    }
+
+    public String getX5u() {
+        return x5u;
+    }
+
+    public void setX5u(String x5u) {
+        this.x5u = x5u;
+    }
+
+    public Set<String> getX5c() {
+        return x5c;
+    }
+
+    public void setX5c(Set<String> x5c) {
+        this.x5c = x5c;
+    }
+
+    public String getX5t() {
+        return x5t;
+    }
+
+    public void setX5t(String x5t) {
+        this.x5t = x5t;
+    }
+
+    public String getX5tS256() {
+        return x5tS256;
+    }
+
+    public void setX5tS256(String x5tS256) {
+        this.x5tS256 = x5tS256;
+    }
+}
diff --git a/gravitee-am-model/src/main/java/io/gravitee/am/model/jose/RSAKey.java b/gravitee-am-model/src/main/java/io/gravitee/am/model/jose/RSAKey.java
new file mode 100644
index 0000000000..92397b5414
--- /dev/null
+++ b/gravitee-am-model/src/main/java/io/gravitee/am/model/jose/RSAKey.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.model.jose;
+
+/**
+ * See <a href="https://tools.ietf.org/html/rfc7638#section-3.2">3.2. JWK Members Used in the Thumbprint Computation</a>
+ *
+ *  The required members for an RSA public key, in lexicographic order, are:
+ *    - "e"
+ *    - "kty"
+ *    - "n"
+ * @author Titouan COMPIEGNE (titouan.compiegne@graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class RSAKey extends JWK {
+
+    /**
+     * The public exponent of the RSA key.
+     */
+    private String e;
+    /**
+     * The modulus value for the RSA key.
+     */
+    private String n;
+
+    public String getE() {
+        return e;
+    }
+
+    public void setE(String e) {
+        this.e = e;
+    }
+
+    public String getN() {
+        return n;
+    }
+
+    public void setN(String n) {
+        this.n = n;
+    }
+}
diff --git a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/src/main/java/io/gravitee/am/plugins/certificate/core/CertificatePluginManager.java b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/src/main/java/io/gravitee/am/plugins/certificate/core/CertificatePluginManager.java
index a180dc9ec7..797c3abdd6 100644
--- a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/src/main/java/io/gravitee/am/plugins/certificate/core/CertificatePluginManager.java
+++ b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/src/main/java/io/gravitee/am/plugins/certificate/core/CertificatePluginManager.java
@@ -20,6 +20,7 @@ import io.gravitee.plugin.core.api.Plugin;
 
 import java.io.IOException;
 import java.util.Collection;
+import java.util.Map;
 
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
@@ -33,7 +34,7 @@ public interface CertificatePluginManager {
 
     Plugin findById(String certificateId);
 
-    CertificateProvider create(String type, String configuration);
+    CertificateProvider create(String type, String configuration, Map<String, Object> metadata);
 
     String getSchema(String certificateId) throws IOException;
 }
diff --git a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/src/main/java/io/gravitee/am/plugins/certificate/core/impl/CertificateMetadataBeanFactoryPostProcessor.java b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/src/main/java/io/gravitee/am/plugins/certificate/core/impl/CertificateMetadataBeanFactoryPostProcessor.java
new file mode 100644
index 0000000000..cc43db634f
--- /dev/null
+++ b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/src/main/java/io/gravitee/am/plugins/certificate/core/impl/CertificateMetadataBeanFactoryPostProcessor.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.plugins.certificate.core.impl;
+
+import io.gravitee.am.certificate.api.CertificateMetadata;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class CertificateMetadataBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
+
+    private final CertificateMetadata metadata;
+
+    CertificateMetadataBeanFactoryPostProcessor(CertificateMetadata metadata) {
+        this.metadata = metadata;
+    }
+
+    @Override
+    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
+        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
+        beanFactory.registerSingleton("metadata", metadata);
+    }
+}
diff --git a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/src/main/java/io/gravitee/am/plugins/certificate/core/impl/CertificatePluginManagerImpl.java b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/src/main/java/io/gravitee/am/plugins/certificate/core/impl/CertificatePluginManagerImpl.java
index 36d251681c..3b3bd36749 100644
--- a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/src/main/java/io/gravitee/am/plugins/certificate/core/impl/CertificatePluginManagerImpl.java
+++ b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/src/main/java/io/gravitee/am/plugins/certificate/core/impl/CertificatePluginManagerImpl.java
@@ -17,6 +17,7 @@ package io.gravitee.am.plugins.certificate.core.impl;
 
 import io.gravitee.am.certificate.api.Certificate;
 import io.gravitee.am.certificate.api.CertificateConfiguration;
+import io.gravitee.am.certificate.api.CertificateMetadata;
 import io.gravitee.am.certificate.api.CertificateProvider;
 import io.gravitee.am.plugins.certificate.core.CertificateConfigurationFactory;
 import io.gravitee.am.plugins.certificate.core.CertificateDefinition;
@@ -76,7 +77,7 @@ public class CertificatePluginManagerImpl implements CertificatePluginManager {
     }
 
     @Override
-    public CertificateProvider create(String type, String configuration) {
+    public CertificateProvider create(String type, String configuration, Map<String, Object> metadata) {
         logger.debug("Looking for a certificate provider for [{}]", type);
         Certificate certificate = certificates.get(type);
 
@@ -84,10 +85,14 @@ public class CertificatePluginManagerImpl implements CertificatePluginManager {
             Class<? extends CertificateConfiguration> configurationClass = certificate.configuration();
             CertificateConfiguration certificateConfiguration = certificateConfigurationFactory.create(configurationClass, configuration);
 
+            CertificateMetadata certificateMetadata = new CertificateMetadata();
+            certificateMetadata.setMetadata(metadata);
+
             return create0(
                     certificatePlugins.get(certificate),
                     certificate.certificateProvider(),
-                    certificateConfiguration);
+                    certificateConfiguration,
+                    certificateMetadata);
         } else {
             logger.error("No certificate provider is registered for type {}", type);
             throw new IllegalStateException("No certificate provider is registered for type " + type);
@@ -113,7 +118,7 @@ public class CertificatePluginManagerImpl implements CertificatePluginManager {
         return null;
     }
 
-    private <T> T create0(Plugin plugin, Class<T> certificateClass, CertificateConfiguration certificateConfiguration) {
+    private <T> T create0(Plugin plugin, Class<T> certificateClass, CertificateConfiguration certificateConfiguration, CertificateMetadata metadata) {
         if (certificateClass == null) {
             return null;
         }
@@ -138,6 +143,10 @@ public class CertificatePluginManagerImpl implements CertificatePluginManager {
                     configurableApplicationContext.addBeanFactoryPostProcessor(
                             new CertificateConfigurationBeanFactoryPostProcessor(certificateConfiguration));
 
+                    // Add certificate metadata bean
+                    configurableApplicationContext.addBeanFactoryPostProcessor(
+                            new CertificateMetadataBeanFactoryPostProcessor(metadata));
+
                     return configurableApplicationContext;
                 }
             });
diff --git a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/oauth2/model/AuthorizationCode.java b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/oauth2/model/AuthorizationCode.java
index f6ac43155e..7a40320b76 100644
--- a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/oauth2/model/AuthorizationCode.java
+++ b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/oauth2/model/AuthorizationCode.java
@@ -15,6 +15,8 @@
  */
 package io.gravitee.am.repository.oauth2.model;
 
+import io.gravitee.common.util.MultiValueMap;
+
 import java.util.Date;
 import java.util.Set;
 
@@ -64,6 +66,11 @@ public class AuthorizationCode {
      */
     private Set<String> scopes;
 
+    /**
+     * The Authorization request parameters
+     */
+    private MultiValueMap<String,String> requestParameters;
+
     public String getId() {
         return id;
     }
@@ -128,6 +135,14 @@ public class AuthorizationCode {
         this.scopes = scopes;
     }
 
+    public MultiValueMap<String,String> getRequestParameters() {
+        return requestParameters;
+    }
+
+    public void setRequestParameters(MultiValueMap<String,String> requestParameters) {
+        this.requestParameters = requestParameters;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoCertificateRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoCertificateRepository.java
index 2e24573d76..f33212d39c 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoCertificateRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoCertificateRepository.java
@@ -26,12 +26,15 @@ import io.reactivex.Maybe;
 import io.reactivex.Observable;
 import io.reactivex.Single;
 import org.bson.Document;
+import org.bson.types.Binary;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.PostConstruct;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import static com.mongodb.client.model.Filters.eq;
 
@@ -98,6 +101,13 @@ public class MongoCertificateRepository extends AbstractManagementMongoRepositor
         certificate.setType(certificateMongo.getType());
         certificate.setConfiguration(certificateMongo.getConfiguration());
         certificate.setDomain(certificateMongo.getDomain());
+        if (certificateMongo.getMetadata() != null) {
+            // convert bson binary type back to byte array
+            Map<String, Object> metadata = certificateMongo.getMetadata().entrySet()
+                    .stream()
+                    .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue() instanceof Binary ? ((Binary) e.getValue()).getData() : e));
+            certificate.setMetadata(metadata);
+        }
         certificate.setCreatedAt(certificateMongo.getCreatedAt());
         certificate.setUpdatedAt(certificateMongo.getUpdatedAt());
         return certificate;
@@ -114,6 +124,7 @@ public class MongoCertificateRepository extends AbstractManagementMongoRepositor
         certificateMongo.setType(certificate.getType());
         certificateMongo.setConfiguration(certificate.getConfiguration());
         certificateMongo.setDomain(certificate.getDomain());
+        certificateMongo.setMetadata(certificate.getMetadata() != null ? new Document(certificate.getMetadata()) : new Document());
         certificateMongo.setCreatedAt(certificate.getCreatedAt());
         certificateMongo.setUpdatedAt(certificate.getUpdatedAt());
         return certificateMongo;
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/CertificateMongo.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/CertificateMongo.java
index c457a7e37d..ee50e0045c 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/CertificateMongo.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/CertificateMongo.java
@@ -16,6 +16,7 @@
 package io.gravitee.am.repository.mongodb.management.internal.model;
 
 import io.gravitee.am.repository.mongodb.common.model.Auditable;
+import org.bson.Document;
 import org.bson.codecs.pojo.annotations.BsonId;
 
 /**
@@ -35,6 +36,8 @@ public class CertificateMongo extends Auditable {
 
     private String domain;
 
+    private Document metadata;
+
     public String getId() {
         return id;
     }
@@ -75,6 +78,14 @@ public class CertificateMongo extends Auditable {
         this.domain = domain;
     }
 
+    public Document getMetadata() {
+        return metadata;
+    }
+
+    public void setMetadata(Document metadata) {
+        this.metadata = metadata;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoAuthorizationCodeRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoAuthorizationCodeRepository.java
index cec513f2a0..0d9c58cc71 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoAuthorizationCodeRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/MongoAuthorizationCodeRepository.java
@@ -22,6 +22,8 @@ import io.gravitee.am.repository.mongodb.common.LoggableIndexSubscriber;
 import io.gravitee.am.repository.mongodb.oauth2.internal.model.AuthorizationCodeMongo;
 import io.gravitee.am.repository.oauth2.api.AuthorizationCodeRepository;
 import io.gravitee.am.repository.oauth2.model.AuthorizationCode;
+import io.gravitee.common.util.LinkedMultiValueMap;
+import io.gravitee.common.util.MultiValueMap;
 import io.reactivex.Maybe;
 import io.reactivex.Observable;
 import io.reactivex.Single;
@@ -30,6 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.PostConstruct;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 import static com.mongodb.client.model.Filters.eq;
@@ -99,6 +102,11 @@ public class MongoAuthorizationCodeRepository extends AbstractOAuth2MongoReposit
         authorizationCode.setRedirectUri(authorizationCodeMongo.getRedirectUri());
         authorizationCode.setScopes(authorizationCodeMongo.getScopes());
 
+        if (authorizationCodeMongo.getRequestParameters() != null) {
+            MultiValueMap<String, String> requestParameters = new LinkedMultiValueMap<>();
+            authorizationCodeMongo.getRequestParameters().entrySet().forEach(entry -> requestParameters.put(entry.getKey(), (List<String>) entry.getValue()));
+            authorizationCode.setRequestParameters(requestParameters);
+        }
         return authorizationCode;
     }
 
@@ -117,6 +125,13 @@ public class MongoAuthorizationCodeRepository extends AbstractOAuth2MongoReposit
         authorizationCodeMongo.setRedirectUri(authorizationCode.getRedirectUri());
         authorizationCodeMongo.setScopes(authorizationCode.getScopes());
 
+        if (authorizationCode.getRequestParameters() != null) {
+            Document document = new Document();
+            authorizationCode.getRequestParameters().forEach((key, value) -> {
+                document.append(key, value);
+            });
+            authorizationCodeMongo.setRequestParameters(document);
+        }
         return authorizationCodeMongo;
     }
 }
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/internal/model/AuthorizationCodeMongo.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/internal/model/AuthorizationCodeMongo.java
index 842c92dd03..c4e95f6707 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/internal/model/AuthorizationCodeMongo.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/oauth2/internal/model/AuthorizationCodeMongo.java
@@ -15,6 +15,7 @@
  */
 package io.gravitee.am.repository.mongodb.oauth2.internal.model;
 
+import org.bson.Document;
 import org.bson.codecs.pojo.annotations.BsonId;
 import org.bson.codecs.pojo.annotations.BsonProperty;
 
@@ -48,6 +49,9 @@ public class AuthorizationCodeMongo {
 
     private Set<String> scopes;
 
+    @BsonProperty("request_parameters")
+    private Document requestParameters;
+
     public String getId() {
         return id;
     }
@@ -112,6 +116,14 @@ public class AuthorizationCodeMongo {
         this.scopes = scopes;
     }
 
+    public Document getRequestParameters() {
+        return requestParameters;
+    }
+
+    public void setRequestParameters(Document requestParameters) {
+        this.requestParameters = requestParameters;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/CertificateService.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/CertificateService.java
index dd2f7f7544..e3888b541b 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/CertificateService.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/CertificateService.java
@@ -42,6 +42,8 @@ public interface CertificateService {
 
     Single<Certificate> update(String domain, String id, UpdateCertificate updateCertificate, String schema);
 
+    Single<Certificate> update(Certificate certificate);
+
     Completable delete(String certificateId);
 
     void setCertificateProviders(Map<String, CertificateProvider> certificateProviders);
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/CertificateServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/CertificateServiceImpl.java
index a2b5185f06..98ec658e7a 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/CertificateServiceImpl.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/CertificateServiceImpl.java
@@ -21,7 +21,6 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
 import io.gravitee.am.certificate.api.CertificateProvider;
 import io.gravitee.am.model.Certificate;
 import io.gravitee.am.plugins.certificate.core.CertificateSchema;
-import io.gravitee.am.repository.exceptions.TechnicalException;
 import io.gravitee.am.repository.management.api.CertificateRepository;
 import io.gravitee.am.service.CertificateService;
 import io.gravitee.am.service.ClientService;
@@ -38,16 +37,9 @@ import io.reactivex.Single;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
-import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.nio.file.FileVisitOption;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.*;
 
 /**
@@ -73,9 +65,6 @@ public class CertificateServiceImpl implements CertificateService {
 
     private ObjectMapper objectMapper = new ObjectMapper();
 
-    @Value("${certificates.path:${gravitee.home}/certificates}")
-    private String certificatesPath;
-
     private Map<String, CertificateProvider> certificateProviders = new HashMap<>();
 
     @Override
@@ -112,7 +101,6 @@ public class CertificateServiceImpl implements CertificateService {
     }
 
     @Override
-    // TODO : refactor (remove file + add JWKS information)
     public Single<Certificate> create(String domain, NewCertificate newCertificate, String schema) {
         LOGGER.debug("Create a new certificate {} for domain {}", newCertificate, domain);
 
@@ -137,20 +125,11 @@ public class CertificateServiceImpl implements CertificateService {
                             try {
                                 JsonNode file = objectMapper.readTree(certificateConfiguration.get(key).asText());
                                 byte[] data = Base64.getDecoder().decode(file.get("content").asText());
-                                File certificateFile = new File(certificatesPath + '/' + domain + '/' + certificateId + '/' + file.get("name").asText());
-                                if (!certificateFile.exists()) {
-                                    certificateFile.getParentFile().mkdirs();
-                                    certificateFile.createNewFile();
-                                }
-                                try (FileOutputStream fop = new FileOutputStream(certificateFile)) {
-                                    fop.write(data);
-                                    fop.flush();
-                                    fop.close();
-
-                                    // update configuration to set the file path
-                                    ((ObjectNode) certificateConfiguration).put(key, certificateFile.getAbsolutePath());
-                                    newCertificate.setConfiguration(objectMapper.writeValueAsString(certificateConfiguration));
-                                }
+                                certificate.setMetadata(Collections.singletonMap("file", data));
+
+                                // update configuration to set the file name
+                                ((ObjectNode) certificateConfiguration).put(key, file.get("name").asText());
+                                newCertificate.setConfiguration(objectMapper.writeValueAsString(certificateConfiguration));
                             } catch (IOException ex) {
                                 LOGGER.error("An error occurs while trying to create certificate binaries", ex);
                                 emitter.onError(ex);
@@ -181,7 +160,6 @@ public class CertificateServiceImpl implements CertificateService {
     }
 
     @Override
-    // TODO : refactor (remove file + add JWKS information)
     public Single<Certificate> update(String domain, String id, UpdateCertificate updateCertificate, String schema) {
         LOGGER.debug("Update a certificate {} for domain {}", id, domain);
 
@@ -210,20 +188,11 @@ public class CertificateServiceImpl implements CertificateService {
                                             if (!oldFileInformation.equals(fileInformation)) {
                                                 JsonNode file = objectMapper.readTree(certificateConfiguration.get(key).asText());
                                                 byte[] data = Base64.getDecoder().decode(file.get("content").asText());
-                                                File certificateFile = new File(certificatesPath + '/' + domain + '/' + oldCertificate.getId() + '/' + file.get("name").asText());
-                                                if (!certificateFile.exists()) {
-                                                    certificateFile.getParentFile().mkdirs();
-                                                    certificateFile.createNewFile();
-                                                }
-                                                try (FileOutputStream fop = new FileOutputStream(certificateFile)) {
-                                                    fop.write(data);
-                                                    fop.flush();
-                                                    fop.close();
-
-                                                    // update configuration to set the file path
-                                                    ((ObjectNode) certificateConfiguration).put(key, certificateFile.getAbsolutePath());
-                                                    updateCertificate.setConfiguration(objectMapper.writeValueAsString(certificateConfiguration));
-                                                }
+                                                oldCertificate.setMetadata(Collections.singletonMap("file", data));
+
+                                                // update configuration to set the file path
+                                                ((ObjectNode) certificateConfiguration).put(key, file.get("name").asText());
+                                                updateCertificate.setConfiguration(objectMapper.writeValueAsString(certificateConfiguration));
                                             }
                                         } catch (IOException ex) {
                                             LOGGER.error("An error occurs while trying to update certificate binaries", ex);
@@ -255,33 +224,36 @@ public class CertificateServiceImpl implements CertificateService {
                 });
     }
 
+    @Override
+    public Single<Certificate> update(Certificate certificate) {
+        // update date
+        certificate.setUpdatedAt(new Date());
+
+        return certificateRepository.update(certificate)
+                .flatMap(certificate1 -> {
+                    // Reload domain to take care about certificate update
+                    return domainService.reload(certificate1.getDomain()).flatMap(domain1 -> Single.just(certificate1));
+                })
+                .doOnError(ex -> {
+                    LOGGER.error("An error occurs while trying to update a certificate", ex);
+                    throw new TechnicalManagementException("An error occurs while trying to update a certificate", ex);
+                });
+    }
+
     @Override
     public Completable delete(String certificateId) {
         LOGGER.debug("Delete certificate {}", certificateId);
         return certificateRepository.findById(certificateId)
                 .switchIfEmpty(Maybe.error(new CertificateNotFoundException(certificateId)))
-                .flatMapSingle(certificate1 -> clientService.findByCertificate(certificateId)
+                .flatMapSingle(certificate -> clientService.findByCertificate(certificateId)
                         .flatMap(clients -> {
                             if (clients.size() > 0) {
                                 throw new CertificateWithClientsException();
                             }
-                            return Single.just(certificate1);
+                            return Single.just(certificate);
                         })
                 )
-                .flatMap(certificate2 -> {
-                    // delete certificate files
-                    try {
-                        Path certificatePath = Paths.get(certificatesPath + '/' + certificate2.getDomain() + '/' + certificateId);
-                        Files.walk(certificatePath, FileVisitOption.FOLLOW_LINKS)
-                                .sorted(Comparator.reverseOrder())
-                                .map(Path::toFile)
-                                .forEach(File::delete);
-                    } catch (IOException ex) {
-                        throw new TechnicalException(ex);
-                    }
-                    return Single.just(certificate2);
-                })
-                .flatMapCompletable(certificate3 -> certificateRepository.delete(certificateId))
+                .flatMapCompletable(certificate -> certificateRepository.delete(certificateId))
                 .onErrorResumeNext(ex -> {
                     LOGGER.error("An error occurs while trying to delete certificate: {}", certificateId, ex);
                     return Completable.error(new TechnicalManagementException(
@@ -317,9 +289,4 @@ public class CertificateServiceImpl implements CertificateService {
             }
         });
     }
-
-    public void setCertificatesPath(String certificatesPath) {
-        this.certificatesPath = certificatesPath;
-    }
-
 }
diff --git a/gravitee-am-ui/src/app/domain/clients/client/settings/settings.component.html b/gravitee-am-ui/src/app/domain/clients/client/settings/settings.component.html
index c09e26e5bc..c9ba62a368 100644
--- a/gravitee-am-ui/src/app/domain/clients/client/settings/settings.component.html
+++ b/gravitee-am-ui/src/app/domain/clients/client/settings/settings.component.html
@@ -144,6 +144,7 @@
     <h4>Certificate</h4>
     <mat-form-field>
       <mat-select placeholder="Certificate" name="certificate" [(ngModel)]="client.certificate">
+        <mat-option><em>None</em></mat-option>
         <mat-option *ngFor="let certificate of certificates" [value]="certificate.id">{{ certificate.name }}</mat-option>
       </mat-select>
       <mat-hint style="font-size: 75%;">Certificate use to sign tokens.</mat-hint>
diff --git a/pom.xml b/pom.xml
index 59d345c5eb..bdcf15aab8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,7 +48,7 @@
         <gravitee-common.version>1.11.0</gravitee-common.version>
         <gravitee-plugin-core.version>1.3.0</gravitee-plugin-core.version>
         <spring-security.version>4.2.4.RELEASE</spring-security.version>
-        <jackson.version>2.9.3</jackson.version>
+        <jackson.version>2.9.5</jackson.version>
         <jetty.version>9.3.14.v20161028</jetty.version>
         <jjwt.version>0.9.0</jjwt.version>
         <vertx.version>3.5.2</vertx.version>


Test patch:
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/oauth2/token/TokenEnhancerTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/oauth2/token/TokenEnhancerTest.java
index ca67c8dd33..e28b5ddb15 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/oauth2/token/TokenEnhancerTest.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/oauth2/token/TokenEnhancerTest.java
@@ -133,7 +133,7 @@ public class TokenEnhancerTest {
         ((TokenEnhancerImpl) tokenEnhancer).setJwtBuilder(jwtBuilder);
 
         when(clientService.findByClientId(anyString())).thenReturn(Maybe.just(client));
-        when(certificateProvider.sign(anyString())).thenReturn(idTokenPayload);
+        when(certificateProvider.sign(anyString())).thenReturn(Single.just(idTokenPayload));
         when(certificateManager.get(anyString())).thenReturn(Maybe.just(certificateProvider));
 
         TestObserver<AccessToken> testObserver = tokenEnhancer.enhance(accessToken, oAuth2Request).test();
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/oidc/jwk/JWKSetServiceTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/oidc/jwk/JWKSetServiceTest.java
new file mode 100644
index 0000000000..2f542979f4
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/oidc/jwk/JWKSetServiceTest.java
@@ -0,0 +1,97 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.gateway.handler.oidc.jwk;
+
+import io.gravitee.am.certificate.api.CertificateProvider;
+import io.gravitee.am.gateway.handler.oauth2.certificate.CertificateManager;
+import io.gravitee.am.gateway.handler.oidc.jwk.impl.JWKSetServiceImpl;
+import io.reactivex.Flowable;
+import io.reactivex.observers.TestObserver;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class JWKSetServiceTest {
+
+    @InjectMocks
+    private JWKSetService jwkSetService = new JWKSetServiceImpl();
+
+    @Mock
+    private CertificateManager certificateManager;
+
+    @Test
+    public void shouldGetJWKSet_singleKey() {
+        io.gravitee.am.model.jose.JWK key = new io.gravitee.am.model.jose.RSAKey();
+        key.setKid("my-test-key");
+
+        CertificateProvider certificateProvider = mock(CertificateProvider.class);
+        when(certificateProvider.keys()).thenReturn(Flowable.just(key));
+
+        when(certificateManager.providers()).thenReturn(Collections.singletonList(certificateProvider));
+
+        TestObserver<JWKSet> testObserver = jwkSetService.getKeys().test();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        testObserver.assertValue(jwkSet -> "my-test-key".equals(jwkSet.getKeys().get(0).getKid()));
+    }
+
+    @Test
+    public void shouldGetJWKSet_multipleKeys() {
+        io.gravitee.am.model.jose.JWK key = new io.gravitee.am.model.jose.RSAKey();
+        key.setKid("my-test-key");
+        io.gravitee.am.model.jose.JWK key2 = new io.gravitee.am.model.jose.RSAKey();
+        key.setKid("my-test-key-2");
+
+        CertificateProvider certificateProvider = mock(CertificateProvider.class);
+        when(certificateProvider.keys()).thenReturn(Flowable.just(key));
+        CertificateProvider certificateProvider2 = mock(CertificateProvider.class);
+        when(certificateProvider2.keys()).thenReturn(Flowable.just(key2));
+
+        when(certificateManager.providers()).thenReturn(Arrays.asList(certificateProvider, certificateProvider2));
+
+        TestObserver<JWKSet> testObserver = jwkSetService.getKeys().test();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        testObserver.assertValue(jwkSet -> jwkSet.getKeys().size() == 2);
+    }
+
+    @Test
+    public void shouldGetJWKSet_noCertificateProvider() {
+        when(certificateManager.providers()).thenReturn(Collections.emptySet());
+
+        TestObserver<JWKSet> testObserver = jwkSetService.getKeys().test();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        testObserver.assertValue(jwkSet -> jwkSet.getKeys().isEmpty());
+    }
+
+}
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/vertx/endpoint/ProviderJWKSetEndpointHandlerTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/vertx/endpoint/ProviderJWKSetEndpointHandlerTest.java
new file mode 100644
index 0000000000..acbad35742
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/src/test/java/io/gravitee/am/gateway/handler/vertx/endpoint/ProviderJWKSetEndpointHandlerTest.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.gateway.handler.vertx.endpoint;
+
+import io.gravitee.am.gateway.handler.oidc.jwk.JWK;
+import io.gravitee.am.gateway.handler.oidc.jwk.JWKSet;
+import io.gravitee.am.gateway.handler.oidc.jwk.JWKSetService;
+import io.gravitee.am.gateway.handler.oidc.jwk.RSAKey;
+import io.gravitee.am.gateway.handler.vertx.RxWebTestBase;
+import io.gravitee.am.gateway.handler.vertx.handler.ExceptionHandler;
+import io.gravitee.am.gateway.handler.vertx.handler.oidc.endpoint.ProviderJWKSetEndpoint;
+import io.gravitee.common.http.HttpStatusCode;
+import io.reactivex.Single;
+import io.vertx.core.http.HttpMethod;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.Collections;
+
+import static org.mockito.Mockito.when;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ProviderJWKSetEndpointHandlerTest extends RxWebTestBase {
+
+    @InjectMocks
+    private ProviderJWKSetEndpoint providerJWKSetEndpoint = new ProviderJWKSetEndpoint();
+
+    @Mock
+    private JWKSetService jwkSetService;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        router.route(HttpMethod.GET, "/.well-known/jwks.json")
+                .handler(providerJWKSetEndpoint);
+        router.route().failureHandler(new ExceptionHandler());
+    }
+
+    @Test
+    public void shouldInvokeJWKSetEndpoint() throws Exception {
+        JWK jwk = new RSAKey();
+        jwk.setKty("RSA");
+        jwk.setKid("my-test-key");
+
+        JWKSet jwkSet = new JWKSet();
+        jwkSet.setKeys(Collections.singletonList(jwk));
+
+        when(jwkSetService.getKeys()).thenReturn(Single.just(jwkSet));
+
+        testRequest(
+                HttpMethod.GET, "/.well-known/jwks.json",
+                HttpStatusCode.OK_200, "OK", "{\n" +
+                        "  \"keys\" : [ {\n" +
+                        "    \"kty\" : \"RSA\",\n" +
+                        "    \"kid\" : \"my-test-key\"\n" +
+                        "  } ]\n" +
+                        "}");
+    }
+
+
+
+
+    @Test
+    public void shouldNotInvokeJWKSetEndpoint_runtimeException() throws Exception {
+        when(jwkSetService.getKeys()).thenReturn(Single.error(new RuntimeException()));
+
+        testRequest(
+                HttpMethod.GET, "/.well-known/jwks.json",
+                HttpStatusCode.INTERNAL_SERVER_ERROR_500, "Internal Server Error");
+    }
+}
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/CertificateUpgraderTest.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/CertificateUpgraderTest.java
new file mode 100644
index 0000000000..b82ea84888
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/CertificateUpgraderTest.java
@@ -0,0 +1,111 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed 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 io.gravitee.am.management.service;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.TextNode;
+import io.gravitee.am.management.service.impl.upgrades.CertificateUpgrader;
+import io.gravitee.am.model.Certificate;
+import io.gravitee.am.service.CertificateService;
+import io.reactivex.Single;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class CertificateUpgraderTest {
+
+    @InjectMocks
+    private CertificateUpgrader certificateUpgrader = new CertificateUpgrader();
+
+    @Mock
+    private CertificateService certificateService;
+
+    @Mock
+    private ObjectMapper objectMapper;
+
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder();
+
+    @Test
+    public void shouldUpdateCertificates_noBinaryFile() throws IOException {
+        // create folder
+        folder.newFolder("test");
+        File file = folder.newFile("test/server.jks");
+
+        Certificate certificate = new Certificate();
+        certificate.setName("certificate-test");
+        certificate.setType("javakeystore-am-certificate");
+        certificate.setConfiguration("{\"jks\":\""+file.getAbsolutePath()+"\"}");
+
+        JsonNode jsonNode = mock(JsonNode.class);
+        TextNode textNode = mock(TextNode.class);
+        when(textNode.asText()).thenReturn(file.getAbsolutePath());
+        when(jsonNode.get("jks")).thenReturn(textNode);
+        when(objectMapper.readTree(anyString())).thenReturn(jsonNode);
+        when(certificateService.findAll()).thenReturn(Single.just(Collections.singletonList(certificate)));
+        when(certificateService.update(any())).thenReturn(Single.just(new Certificate()));
+
+        certificateUpgrader.upgrade();
+
+        verify(certificateService, times(1)).findAll();
+        verify(certificateService, times(1)).update(any());
+    }
+
+    @Test
+    public void shouldNotUpdateCertificates_binaryFile() {
+        Certificate certificate = new Certificate();
+        certificate.setName("certificate-test");
+        certificate.setType("javakeystore-am-certificate");
+        certificate.setMetadata(Collections.singletonMap("file", new File("")));
+
+        when(certificateService.findAll()).thenReturn(Single.just(Collections.singletonList(certificate)));
+        certificateUpgrader.upgrade();
+
+        verify(certificateService, times(1)).findAll();
+        verify(certificateService, never()).update(any());
+    }
+
+    @Test
+    public void shouldNotUpdateCertificates_noJavaKeyStoreCertificate() {
+        Certificate certificate = new Certificate();
+        certificate.setName("certificate-test");
+        certificate.setType("unknown");
+        certificate.setMetadata(Collections.singletonMap("file", new File("")));
+
+        when(certificateService.findAll()).thenReturn(Single.just(Collections.singletonList(certificate)));
+        certificateUpgrader.upgrade();
+
+        verify(certificateService, times(1)).findAll();
+        verify(certificateService, never()).update(any());
+    }
+}
diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/CertificateServiceTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/service/CertificateServiceTest.java
index 629235739b..995b186b87 100644
--- a/gravitee-am-service/src/test/java/io/gravitee/am/service/CertificateServiceTest.java
+++ b/gravitee-am-service/src/test/java/io/gravitee/am/service/CertificateServiceTest.java
@@ -26,17 +26,13 @@ import io.reactivex.Completable;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
 import io.reactivex.observers.TestObserver;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
 
-import java.io.File;
-import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
 
@@ -64,9 +60,6 @@ public class CertificateServiceTest {
     @Mock
     private ObjectMapper objectMapper;
 
-    @Rule
-    public TemporaryFolder folder = new TemporaryFolder();
-
     private final static String DOMAIN = "domain1";
 
     @Test
@@ -122,11 +115,7 @@ public class CertificateServiceTest {
     }
 
     @Test
-    public void shouldDelete() throws IOException {
-        // prepare file system
-        folder.newFolder(DOMAIN);
-        File file = folder.newFile(DOMAIN + "/my-certificate");
-        ((CertificateServiceImpl) certificateService).setCertificatesPath(file.toPath().getParent().getParent().toString());
+    public void shouldDelete() {
         // prepare certificate
         Certificate certificate = Mockito.mock(Certificate.class);
         when(certificate.getDomain()).thenReturn(DOMAIN);