Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions vertx-auth-common/azure.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/bin/bash

#
# Copyright (c) 2011-2026 Contributors to the Eclipse Foundation
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#

# Generated with Gemini

###############################################################################
# UNDERSTANDING THE JSON FIELDS (JWK - RFC 7517)
#
# This script generates a mock Microsoft discovery file (azure-sample-keys.json)
# and a signed JWT (azure-sample-token.txt).
#
# Field | Description
# ------|----------------------------------------------------------------------
# kid | Key ID: Must match the 'kid' in the JWT header. Used to find the key.
# kty | Key Type: Set to 'RSA'.
# use | Public Key Use: Set to 'sig' (signature).
# n | Modulus: The "large number" part of the RSA public key (Base64Url).
# e | Exponent: The RSA public exponent, usually 'AQAB' (decimal 65537).
# x5c | Cert Chain: Base64-encoded X.509 certificate (Standard Base64).
# x5t | Thumbprint: SHA-1 hash of the DER-encoded certificate (Base64Url).
###############################################################################

# 1. Configuration
KID="test-kid-2026"
TARGET_DIR="src/test/resources"
JWKS_FILE="$TARGET_DIR/azure-sample-keys.json"
TOKEN_FILE="$TARGET_DIR/azure-sample-token.txt"

# Ensure the directory exists
mkdir -p "$TARGET_DIR"

# 2. Generate Temporary Keys
# We use temporary names to ensure we don't overwrite anything important
openssl genrsa -out temp_private.pem 2048 2>/dev/null
openssl req -new -x509 -key temp_private.pem -out temp_cert.pem -days 10950 -subj "/CN=login.microsoftonline.test" 2>/dev/null

# 3. Helper function for Base64Url encoding that handles different OS flags
b64url() {
# Try -w0 (Linux/GNU), if it fails, use -b0 (macOS/BSD), then remove newlines manually just in case
(base64 -w0 2>/dev/null || base64 -b0 2>/dev/null || base64) | tr -d '\n=' | tr '+/' '-_'
}

# 4. Extract Components for JWKS
MOD=$(openssl rsa -in temp_private.pem -noout -modulus | cut -d'=' -f2 | xxd -r -p | b64url)
X5C=$(openssl x509 -in temp_cert.pem -outform DER | base64 | tr -d '\n')
X5T=$(openssl x509 -in temp_cert.pem -fingerprint -noout | cut -d'=' -f2 | sed 's/://g' | xxd -r -p | b64url)

# 5. Create the JWKS file
printf '{\n "keys": [\n {\n "kty": "RSA",\n "use": "sig",\n "kid": "%s",\n "x5t": "%s",\n "n": "%s",\n "e": "AQAB",\n "x5c": ["%s"]\n }\n ]\n}\n' \
"$KID" "$X5T" "$MOD" "$X5C" > "$JWKS_FILE"

# 6. Generate and Sign the Token
HEADER='{"alg":"RS256","typ":"JWT","kid":"'$KID'"}'
PAYLOAD='{"iss":"https://sts.windows.net/test-tenant/","sub":"junit-test","aud":"your-client-id","iat":1704067200,"exp":2524608000}'

H_B64=$(printf '%s' "$HEADER" | b64url)
P_B64=$(printf '%s' "$PAYLOAD" | b64url)

# IMPORTANT: Sign the "HEADER.PAYLOAD" string
# 'openssl dgst' can sometimes add metadata, so we pipe strictly the string
SIG=$(printf '%s.%s' "$H_B64" "$P_B64" | openssl dgst -sha256 -sign temp_private.pem -binary | b64url)

# Save the token as a single continuous line with NO trailing characters
printf "%s.%s.%s" "$H_B64" "$P_B64" "$SIG" | tr -d '[:space:]' > "$TOKEN_FILE"

# 7. Cleanup
rm temp_private.pem
rm temp_cert.pem

echo "Cleanup complete. Assets generated in $TARGET_DIR:"
echo " - $JWKS_FILE"
echo " - $TOKEN_FILE"
11 changes: 0 additions & 11 deletions vertx-auth-common/src/main/java/io/vertx/ext/auth/JWTOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ public class JWTOptions {
private String issuer;
private String subject;
private List<String> permissions;
private String nonceAlgorithm;

public JWTOptions() {
header = new JsonObject();
Expand All @@ -38,7 +37,6 @@ public JWTOptions(JWTOptions other) {
this.issuer = other.issuer;
this.subject = other.subject;
this.permissions = other.permissions == null ? null : new ArrayList<>(other.permissions);
this.nonceAlgorithm = other.nonceAlgorithm;
}

public JWTOptions(JsonObject json) {
Expand Down Expand Up @@ -145,13 +143,4 @@ public JWTOptions setSubject(String subject) {
this.subject = subject;
return this;
}

public String getNonceAlgorithm() {
return nonceAlgorithm;
}

public JWTOptions setNonceAlgorithm(String nonceAlgorithm) {
this.nonceAlgorithm = nonceAlgorithm;
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ public final class JWT {

private boolean allowEmbeddedKey = false;
private X509Certificate rootCA;
private MessageDigest nonceDigest;

// keep 2 maps (1 for sing, 1 for verify) this simplifies the lookups
private final Map<String, List<JWS>> SIGN = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -116,19 +115,6 @@ public JWT embeddedKeyRootCA(String rootCA) throws CertificateException {
return this;
}

public JWT nonceAlgorithm(String alg) {
if (alg == null) {
nonceDigest = null;
} else {
try {
nonceDigest = MessageDigest.getInstance(alg);
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException(e);
}
}
return this;
}

private void addJWK(List<JWS> current, JWK jwk) {
boolean replaced = false;
for (int i = 0; i < current.size(); i++) {
Expand Down Expand Up @@ -281,16 +267,6 @@ public JsonObject decode(final String token, boolean full, List<X509CRL> crls) t
throw new SignatureException("missing signature segment");
}
byte[] payloadInput = base64UrlDecode(signatureSeg);
if (nonceDigest != null && header.containsKey("nonce")) {
// this is an Azure Graph extension, a nonce is added to the token
// after the serialization. The original value is the digest of the
// post value.
synchronized (this) {
nonceDigest.reset();
header.put("nonce", base64UrlEncode(nonceDigest.digest(header.getString("nonce").getBytes(StandardCharsets.UTF_8))));
headerSeg = base64UrlEncode(header.encode().getBytes(StandardCharsets.UTF_8));
}
}
byte[] signingInput = (headerSeg + "." + payloadSeg).getBytes(UTF8);

String kid = header.getString("kid");
Expand Down
15 changes: 5 additions & 10 deletions vertx-auth-common/src/test/java/io/vertx/tests/JWKTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import io.vertx.ext.auth.JWTOptions;
import io.vertx.ext.auth.impl.jose.JWK;
import io.vertx.ext.auth.impl.jose.JWT;
import org.junit.Ignore;
import org.junit.Test;

import java.io.IOException;
Expand Down Expand Up @@ -173,22 +172,18 @@ public void publicECK() {
new JWK(jwk);
}

@Ignore("Certificate expired")
@Test
public void loadAzure() throws Exception {
Vertx vertx = Vertx.vertx();
JsonObject azure = new JsonObject(vertx.fileSystem()
.readFileBlocking("azure.json"));
JsonObject azureSampleKeys = new JsonObject(vertx.fileSystem().readFileBlocking("azure-sample-keys.json"));
String azureSampleToken = vertx.fileSystem().readFileBlocking("azure-sample-token.txt").toString();

JWK key = new JWK(azure.getJsonArray("keys").getJsonObject(1));

JWT jwt = new JWT()
.addJWK(key)
.nonceAlgorithm("SHA-256");
JWK key = new JWK(azureSampleKeys.getJsonArray("keys").getJsonObject(0));

JWT jwt = new JWT().addJWK(key);

System.out.println(
jwt.decode("eyJ0eXAiOiJKV1QiLCJub25jZSI6InM5TzdaZ2F6WVJEd2VCZzZhbDNWVkhuZFFOQ0JHSVZZaDMxTDZRVFljTDAiLCJhbGciOiJSUzI1NiIsIng1dCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyIsImtpZCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyJ9.eyJhdWQiOiIwMDAwMDAwMy0wMDAwLTAwMDAtYzAwMC0wMDAwMDAwMDAwMDAiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9mY2FhZjcxMC03YzllLTRjYTAtOWNkYS02OTczOWZhZmJhNGIvIiwiaWF0IjoxNjExNjg3NDE1LCJuYmYiOjE2MTE2ODc0MTUsImV4cCI6MTYxMTY5MTMxNSwiYWNjdCI6MCwiYWNyIjoiMSIsImFjcnMiOlsidXJuOnVzZXI6cmVnaXN0ZXJzZWN1cml0eWluZm8iLCJ1cm46bWljcm9zb2Z0OnJlcTEiLCJ1cm46bWljcm9zb2Z0OnJlcTIiLCJ1cm46bWljcm9zb2Z0OnJlcTMiLCJjMSIsImMyIiwiYzMiLCJjNCIsImM1IiwiYzYiLCJjNyIsImM4IiwiYzkiLCJjMTAiLCJjMTEiLCJjMTIiLCJjMTMiLCJjMTQiLCJjMTUiLCJjMTYiLCJjMTciLCJjMTgiLCJjMTkiLCJjMjAiLCJjMjEiLCJjMjIiLCJjMjMiLCJjMjQiLCJjMjUiXSwiYWlvIjoiQVVRQXUvOFNBQUFBMFYwa0M4S25EM01RbUJ0WGk4OG9lT3NzT2xzU0JLZ1Rld1diQ1RnV2x0MEZ4dG9POVZtVjVjOGRLTStNckE1MXJoZW1Ebm9HMGJGUkRJWkpnM3Izb0E9PSIsImFtciI6WyJwd2QiLCJtZmEiXSwiYXBwX2Rpc3BsYXluYW1lIjoicG9iLXNlcnZlciIsImFwcGlkIjoiMTNjOWUxMTItYjE1OC00YjE5LThkNTctZTFmMzg4MWM0MzgzIiwiYXBwaWRhY3IiOiIxIiwiZmFtaWx5X25hbWUiOiJMb3BlcyIsImdpdmVuX25hbWUiOiJQYXVsbyIsImlkdHlwIjoidXNlciIsImlwYWRkciI6IjIxNy4xMDIuMTY1LjQ2IiwibmFtZSI6IkxhYiBQYXVsbyBMb3BlcyIsIm9pZCI6IjUyYmY4YWMyLTNlODMtNGNmNC04MTYxLTBiMTM1YWUwNjk4YiIsInBsYXRmIjoiMTQiLCJwdWlkIjoiMTAwMzIwMDEwRjQzQjcyRSIsInJoIjoiMC5BQUFBRVBlcV9KNThvRXljMm1sem42LTZTeExoeVJOWXNSbExqVmZoODRnY1E0TjVBRHMuIiwic2NwIjoiZW1haWwgb3BlbmlkIHByb2ZpbGUgVXNlci5SZWFkIiwic2lnbmluX3N0YXRlIjpbImttc2kiXSwic3ViIjoiWXltcTZLUmlLMHVUX1NVZ0JzYWUtaElIRE5VX0FLQVpvRG5TTWwxR3VpZyIsInRlbmFudF9yZWdpb25fc2NvcGUiOiJFVSIsInRpZCI6ImZjYWFmNzEwLTdjOWUtNGNhMC05Y2RhLTY5NzM5ZmFmYmE0YiIsInVuaXF1ZV9uYW1lIjoicGF1bG9AbGFiLnRlbnRpeG8uY29tIiwidXBuIjoicGF1bG9AbGFiLnRlbnRpeG8uY29tIiwidXRpIjoiZGNzM1ZzMXk5VW1SOXBzV05JaUxBQSIsInZlciI6IjEuMCIsIndpZHMiOlsiNjJlOTAzOTQtNjlmNS00MjM3LTkxOTAtMDEyMTc3MTQ1ZTEwIiwiYjc5ZmJmNGQtM2VmOS00Njg5LTgxNDMtNzZiMTk0ZTg1NTA5Il0sInhtc19zdCI6eyJzdWIiOiJiNGRXNmVuWk9fRGhFLUMyZE1Qdks5Y1JXcW8zXy1FQVc5MVJ4WmRKNnNVIn0sInhtc190Y2R0IjoxNjEwOTEyOTM2fQ.M4dXPZszAsL_rnceagjZnmd8yzbbB3hou4L6vVLGzqAt4wVg8KwQKxcxIGqBqgDWRlBvLYqWs61dvt8vSa-9GaMJifHwmfWoXPvyVzdxhx3qrqgHdsz1HWX5WzcDlEbHrPXZGE8KM-0czE67rePMxEHK7vLf5TbmERLGJt4QDOGZxVHYvnplIrIM1eGjANIeWYTyW5g-YDx3VX6yVl5QHvP4CFdINhDV7i-L3bjmV4M8F6wYs7Xs7nIrKYEiyjTrpXGUL7u29eHXgzeGlSxfXeqTdYmgEt6lFOxx-fZzO0m92AhPGGAe6IB85FtqSi5T95Nif3pHPsouryhDco8y3g")
jwt.decode(azureSampleToken).encodePrettily()
);
}

Expand Down
15 changes: 15 additions & 0 deletions vertx-auth-common/src/test/resources/azure-sample-keys.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"kid": "test-kid-2026",
"x5t": "DKOCmlcao8v6VzwsfMZdBPYSwgw",
"n": "igMDD4eqJc_gwbwPN9mg_19y59p-a8erLALe5g0xoTvJaizMHZdOevDkgwSX22z3XWX38qfSLhf_BrzMIRKdQCeKxw4blOk__FqoC8dZn0d48f3hqvY4NGqe0JVAw9ReKBz0JZHrxTnt12KiDfm8FGrcG-Ku-MuW5RPv1jg_s7wD5NgCVpy1WujcffKySa7-HI-9fkIfRd4XYFS5vsqS-2CTuazZdnMlP4lqJ5AmoJetMXIIgVHuPOnXwWjKEd3ywpOJO4jrPoBO3p3kruPUXz4vBhTtS9OfszeZPfdP-edcoP-MT2L2v3wdwFeEACunhuVMz-FEOCTyWSlYIWJpYw",
"e": "AQAB",
"x5c": [
"MIIDLTCCAhWgAwIBAgIUK6goEG+ypWIUui1TmUT9w7WKUk4wDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwabG9naW4ubWljcm9zb2Z0b25saW5lLnRlc3QwIBcNMjYwMTEzMTM0NTMzWhgPMjA1NjAxMDYxMzQ1MzNaMCUxIzAhBgNVBAMMGmxvZ2luLm1pY3Jvc29mdG9ubGluZS50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAigMDD4eqJc/gwbwPN9mg/19y59p+a8erLALe5g0xoTvJaizMHZdOevDkgwSX22z3XWX38qfSLhf/BrzMIRKdQCeKxw4blOk//FqoC8dZn0d48f3hqvY4NGqe0JVAw9ReKBz0JZHrxTnt12KiDfm8FGrcG+Ku+MuW5RPv1jg/s7wD5NgCVpy1WujcffKySa7+HI+9fkIfRd4XYFS5vsqS+2CTuazZdnMlP4lqJ5AmoJetMXIIgVHuPOnXwWjKEd3ywpOJO4jrPoBO3p3kruPUXz4vBhTtS9OfszeZPfdP+edcoP+MT2L2v3wdwFeEACunhuVMz+FEOCTyWSlYIWJpYwIDAQABo1MwUTAdBgNVHQ4EFgQUY3EXqAtFxyVz/USG7rLU05X9wMEwHwYDVR0jBBgwFoAUY3EXqAtFxyVz/USG7rLU05X9wMEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAGyI9jRKyLXXpGaxIl8usfNj7Z7AzrPyF9gczRBd1/a+KB73Lb2jkCq7RrhpOiWfKnXksWHDHK/jMYDXHFDg+b/DRWN+iCXug9KFezvYDa+UN4OEzFZb+gC5MGJskCFNsX4i1OrYO+GrsPpv54U6wrgWjSP8GFPobug1M3qHDf+xXV+/MlBqJoaLNbzM5yuZYMWvdD/nOsJ+nLcmWIhDw29fmi3hcimbeUHTcAZn+hHluzMqmTOyXmOx0bTbQQnKRMeyc273kc8eRnRZlehB3gL5GRJS2a8FdxvhZNfDIlo8VvWDcsrYVYOiLXg0QU72YRPvlI9v+v5i9v1Qpq67g/w=="
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InRlc3Qta2lkLTIwMjYifQ.eyJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC90ZXN0LXRlbmFudC8iLCJzdWIiOiJqdW5pdC10ZXN0IiwiYXVkIjoieW91ci1jbGllbnQtaWQiLCJpYXQiOjE3MDQwNjcyMDAsImV4cCI6MjUyNDYwODAwMH0.KOy_yvvHTTDP-cXkjSBWdJWTOmeA1dMQtYIMWL64D-2UmyDbEoLeOFhHoGrVtQkT3uiPx2GgpAwYqu3DbZOxj_1t4fJK7C21FgFMs9a7P7z2D7zeq-w-OqRO63B2MoztjgzUx6aNMDDkdCI4HA76NKhv49TVnRfQmGlCfj5xlfBAcEM6ytelOgnRdlA5rWnkIiNdkBjuRpbykluRQl03Y2NdjP8FEJoQ-QNIby_uKKhOe50fy6a04MZ7njtVA-i_b4Oq0HzJfsqkWi2Kss7M_yPA3uuD-nSx5mR8tFkumamhHNsNNRsFZvTAoQlo7Ohw786bR4WLb-DfEjXORWxLLA
48 changes: 0 additions & 48 deletions vertx-auth-common/src/test/resources/azure.json

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ public class JWTAuthProviderImpl implements JWTAuth {

public JWTAuthProviderImpl(Vertx vertx, JWTAuthOptions config) {
this.jwtOptions = config.getJWTOptions();
// set the nonce algorithm
jwt.nonceAlgorithm(jwtOptions.getNonceAlgorithm());

final KeyStoreOptions keyStore = config.getKeyStore();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,6 @@ public OAuth2AuthProviderImpl(Vertx vertx, OAuth2Options config) {
this.config.replaceVariables(true);
this.config.validate();

// set the nonce algorithm
jwt.nonceAlgorithm(this.config.getJWTOptions().getNonceAlgorithm());

if (config.getPubSecKeys() != null) {
for (PubSecKeyOptions pubSecKey : config.getPubSecKeys()) {
try {
Expand Down Expand Up @@ -131,10 +128,6 @@ public Future<Void> jWKSet() {
((VertxInternal) vertx).removeCloseHook(this);
}

JWT jwt = new JWT()
// set the nonce algorithm
.nonceAlgorithm(config.getJWTOptions().getNonceAlgorithm());

JsonArray keys = json.getJsonArray("keys");
for (Object key : keys) {
try {
Expand Down
Loading
Loading