diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java
index f261186045c1c..19419851a9474 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java
@@ -176,14 +176,32 @@ public void setIncludeClientId(boolean includeClientId) {
/**
* Configuration of the certificate chain which can be used to verify tokens.
- * If the certificate chain trusstore is configured, the tokens can be verified using the certificate
+ * If the certificate chain truststore is configured, the tokens can be verified using the certificate
* chain inlined in the Base64-encoded format as an `x5c` header in the token itself.
+ *
+ * The certificate chain inlined in the token is verified.
+ * Signature of every certificate in the chain but the root certificate is verified by the next certificate in the chain.
+ * Thumbprint of the root certificate in the chain must match a thumbprint of one of the certificates in the truststore.
+ *
+ * Additionally, a direct trust in the leaf chain certificate which will be used to verify the token signature must
+ * be established.
+ * By default, the leaf certificate's thumbprint must match a thumbprint of one of the certificates in the truststore.
+ * If the truststore does not have the leaf certificate imported, then the leaf certificate must be identified by its Common
+ * Name.
*/
@ConfigItem
public CertificateChain certificateChain = new CertificateChain();
@ConfigGroup
public static class CertificateChain {
+ /**
+ * Common name of the leaf certificate. It must be set if the {@link #trustStoreFile} does not have
+ * this certificate imported.
+ *
+ */
+ @ConfigItem
+ public Optional leafCertificateName = Optional.empty();
+
/**
* Truststore file which keeps thumbprints of the trusted certificates.
*/
@@ -233,6 +251,14 @@ public Optional getTrustStoreFileType() {
public void setTrustStoreFileType(Optional trustStoreFileType) {
this.trustStoreFileType = trustStoreFileType;
}
+
+ public Optional getLeafCertificateName() {
+ return leafCertificateName;
+ }
+
+ public void setLeafCertificateName(String leafCertificateName) {
+ this.leafCertificateName = Optional.of(leafCertificateName);
+ }
}
/**
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CertChainPublicKeyResolver.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CertChainPublicKeyResolver.java
index ae0105fce3bcf..069ad2efb7704 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CertChainPublicKeyResolver.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CertChainPublicKeyResolver.java
@@ -3,6 +3,7 @@
import java.security.Key;
import java.security.cert.X509Certificate;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
import org.jboss.logging.Logger;
@@ -12,11 +13,13 @@
import io.quarkus.oidc.OidcTenantConfig.CertificateChain;
import io.quarkus.runtime.configuration.ConfigurationException;
+import io.quarkus.security.runtime.X509IdentityProvider;
import io.vertx.ext.auth.impl.CertificateHelper;
public class CertChainPublicKeyResolver implements RefreshableVerificationKeyResolver {
private static final Logger LOG = Logger.getLogger(OidcProvider.class);
final Set thumbprints;
+ final Optional expectedLeafCertificateName;
public CertChainPublicKeyResolver(CertificateChain chain) {
if (chain.trustStorePassword.isEmpty()) {
@@ -25,6 +28,7 @@ public CertChainPublicKeyResolver(CertificateChain chain) {
}
this.thumbprints = TrustStoreUtils.getTrustedCertificateThumbprints(chain.trustStoreFile.get(),
chain.trustStorePassword.get(), chain.trustStoreCertAlias, chain.getTrustStoreFileType());
+ this.expectedLeafCertificateName = chain.leafCertificateName;
}
@Override
@@ -37,9 +41,29 @@ public Key resolveKey(JsonWebSignature jws, List nestingContex
LOG.debug("Token does not have an 'x5c' certificate chain header");
return null;
}
- String thumbprint = TrustStoreUtils.calculateThumprint(chain.get(0));
- if (!thumbprints.contains(thumbprint)) {
- throw new UnresolvableKeyException("Certificate chain thumprint is invalid");
+ if (chain.size() == 0) {
+ LOG.debug("Token 'x5c' certificate chain is empty");
+ return null;
+ }
+ LOG.debug("Checking a thumbprint of the root chain certificate");
+ String rootThumbprint = TrustStoreUtils.calculateThumprint(chain.get(chain.size() - 1));
+ if (!thumbprints.contains(rootThumbprint)) {
+ LOG.error("Thumprint of the root chain certificate is invalid");
+ throw new UnresolvableKeyException("Thumprint of the root chain certificate is invalid");
+ }
+ if (expectedLeafCertificateName.isEmpty()) {
+ LOG.debug("Checking a thumbprint of the leaf chain certificate");
+ String thumbprint = TrustStoreUtils.calculateThumprint(chain.get(0));
+ if (!thumbprints.contains(thumbprint)) {
+ LOG.error("Thumprint of the leaf chain certificate is invalid");
+ throw new UnresolvableKeyException("Thumprint of the leaf chain certificate is invalid");
+ }
+ } else {
+ String leafCertificateName = X509IdentityProvider.getCommonName(chain.get(0).getSubjectX500Principal());
+ if (!expectedLeafCertificateName.get().equals(leafCertificateName)) {
+ LOG.errorf("Wrong leaf certificate common name: %s", leafCertificateName);
+ throw new UnresolvableKeyException("Wrong leaf certificate common name");
+ }
}
//TODO: support revocation lists
CertificateHelper.checkValidity(chain, null);
@@ -50,6 +74,8 @@ public Key resolveKey(JsonWebSignature jws, List nestingContex
root.verify(root.getPublicKey());
}
return chain.get(0).getPublicKey();
+ } catch (UnresolvableKeyException ex) {
+ throw ex;
} catch (Exception ex) {
throw new UnresolvableKeyException("Invalid certificate chain", ex);
}
diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/X509IdentityProvider.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/X509IdentityProvider.java
index d7bcff7deb67c..63d79961e261b 100644
--- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/X509IdentityProvider.java
+++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/X509IdentityProvider.java
@@ -60,7 +60,7 @@ private Set extractRoles(X509Certificate certificate, Map