You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kafka.apache.org by ju...@apache.org on 2021/11/16 00:21:59 UTC

[kafka] branch trunk updated: KAFKA-13445: Add ECDSA test for JWT validation (#11487)

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

junrao pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/kafka.git


The following commit(s) were added to refs/heads/trunk by this push:
     new f7031aa  KAFKA-13445: Add ECDSA test for JWT validation (#11487)
f7031aa is described below

commit f7031aa8073097e806d011b1aacf967e2a173a9c
Author: Kirk True <ki...@mustardgrain.com>
AuthorDate: Mon Nov 15 16:20:27 2021 -0800

    KAFKA-13445: Add ECDSA test for JWT validation (#11487)
    
    Reviewers: Jun Rao <ju...@gmail.com>
---
 .../oauthbearer/secured/AccessTokenBuilder.java    | 61 +++++++++-------------
 .../OAuthBearerLoginCallbackHandlerTest.java       |  5 +-
 .../oauthbearer/secured/OAuthBearerTest.java       | 24 +++++++++
 .../OAuthBearerValidatorCallbackHandlerTest.java   |  8 ++-
 .../secured/ValidatorAccessTokenValidatorTest.java | 33 ++++++++----
 5 files changed, 83 insertions(+), 48 deletions(-)

diff --git a/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/AccessTokenBuilder.java b/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/AccessTokenBuilder.java
index 20def92..24a40aa 100644
--- a/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/AccessTokenBuilder.java
+++ b/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/AccessTokenBuilder.java
@@ -24,9 +24,7 @@ import java.io.IOException;
 import java.util.Collection;
 import org.apache.kafka.common.utils.MockTime;
 import org.apache.kafka.common.utils.Time;
-import org.jose4j.jwk.RsaJsonWebKey;
-import org.jose4j.jwk.RsaJwkGenerator;
-import org.jose4j.jws.AlgorithmIdentifiers;
+import org.jose4j.jwk.PublicJsonWebKey;
 import org.jose4j.jws.JsonWebSignature;
 import org.jose4j.jwt.ReservedClaimNames;
 import org.jose4j.lang.JoseException;
@@ -35,40 +33,40 @@ public class AccessTokenBuilder {
 
     private final ObjectMapper objectMapper = new ObjectMapper();
 
+    private String alg;
+
     private String audience;
 
     private String subject = "jdoe";
 
-    private String subjectClaimName = ReservedClaimNames.SUBJECT;
+    private final String subjectClaimName = ReservedClaimNames.SUBJECT;
 
     private Object scope = "engineering";
 
-    private String scopeClaimName = "scope";
+    private final String scopeClaimName = "scope";
 
-    private Long issuedAtSeconds;
+    private final Long issuedAtSeconds;
 
     private Long expirationSeconds;
 
-    private RsaJsonWebKey jwk;
+    private PublicJsonWebKey jwk;
 
-    public AccessTokenBuilder() throws JoseException {
+    public AccessTokenBuilder() {
         this(new MockTime());
     }
 
-    public AccessTokenBuilder(Time time) throws JoseException {
+    public AccessTokenBuilder(Time time) {
         this.issuedAtSeconds = time.milliseconds() / 1000;
         this.expirationSeconds = this.issuedAtSeconds + 60;
-        this.jwk = createJwk();
     }
 
-    public static RsaJsonWebKey createJwk() throws JoseException {
-        RsaJsonWebKey jwk = RsaJwkGenerator.generateJwk(2048);
-        jwk.setKeyId("key-1");
-        return jwk;
+    public String alg() {
+        return alg;
     }
 
-    public String audience() {
-        return audience;
+    public AccessTokenBuilder alg(String alg) {
+        this.alg = alg;
+        return this;
     }
 
     public AccessTokenBuilder audience(String audience) {
@@ -89,11 +87,6 @@ public class AccessTokenBuilder {
         return subjectClaimName;
     }
 
-    public AccessTokenBuilder subjectClaimName(String subjectClaimName) {
-        this.subjectClaimName = subjectClaimName;
-        return this;
-    }
-
     public Object scope() {
         return scope;
     }
@@ -118,20 +111,10 @@ public class AccessTokenBuilder {
         return scopeClaimName;
     }
 
-    public AccessTokenBuilder scopeClaimName(String scopeClaimName) {
-        this.scopeClaimName = scopeClaimName;
-        return this;
-    }
-
     public Long issuedAtSeconds() {
         return issuedAtSeconds;
     }
 
-    public AccessTokenBuilder issuedAtSeconds(Long issuedAtSeconds) {
-        this.issuedAtSeconds = issuedAtSeconds;
-        return this;
-    }
-
     public Long expirationSeconds() {
         return expirationSeconds;
     }
@@ -141,11 +124,11 @@ public class AccessTokenBuilder {
         return this;
     }
 
-    public RsaJsonWebKey jwk() {
+    public PublicJsonWebKey jwk() {
         return jwk;
     }
 
-    public AccessTokenBuilder jwk(RsaJsonWebKey jwk) {
+    public AccessTokenBuilder jwk(PublicJsonWebKey jwk) {
         this.jwk = jwk;
         return this;
     }
@@ -183,9 +166,15 @@ public class AccessTokenBuilder {
 
         JsonWebSignature jws = new JsonWebSignature();
         jws.setPayload(json);
-        jws.setKey(jwk.getPrivateKey());
-        jws.setKeyIdHeaderValue(jwk.getKeyId());
-        jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
+
+        if (jwk != null) {
+            jws.setKey(jwk.getPrivateKey());
+            jws.setKeyIdHeaderValue(jwk.getKeyId());
+        }
+
+        if (alg != null)
+            jws.setAlgorithmHeaderValue(alg);
+
         return jws.getCompactSerialization();
     }
 
diff --git a/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/OAuthBearerLoginCallbackHandlerTest.java b/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/OAuthBearerLoginCallbackHandlerTest.java
index 4be823e..ab823a3 100644
--- a/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/OAuthBearerLoginCallbackHandlerTest.java
+++ b/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/OAuthBearerLoginCallbackHandlerTest.java
@@ -40,6 +40,7 @@ import org.apache.kafka.common.security.oauthbearer.OAuthBearerToken;
 import org.apache.kafka.common.security.oauthbearer.OAuthBearerTokenCallback;
 import org.apache.kafka.common.security.oauthbearer.internals.OAuthBearerClientInitialResponse;
 import org.apache.kafka.common.utils.Utils;
+import org.jose4j.jws.AlgorithmIdentifiers;
 import org.junit.jupiter.api.Test;
 
 public class OAuthBearerLoginCallbackHandlerTest extends OAuthBearerTest {
@@ -47,7 +48,9 @@ public class OAuthBearerLoginCallbackHandlerTest extends OAuthBearerTest {
     @Test
     public void testHandleTokenCallback() throws Exception {
         Map<String, ?> configs = getSaslConfigs();
-        AccessTokenBuilder builder = new AccessTokenBuilder();
+        AccessTokenBuilder builder = new AccessTokenBuilder()
+            .jwk(createRsaJwk())
+            .alg(AlgorithmIdentifiers.RSA_USING_SHA256);
         String accessToken = builder.build();
         AccessTokenRetriever accessTokenRetriever = () -> accessToken;
 
diff --git a/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/OAuthBearerTest.java b/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/OAuthBearerTest.java
index 6fec08d..5edb0b0 100644
--- a/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/OAuthBearerTest.java
+++ b/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/OAuthBearerTest.java
@@ -45,6 +45,10 @@ import org.apache.kafka.common.security.auth.AuthenticateCallbackHandler;
 import org.apache.kafka.common.security.authenticator.TestJaasConfig;
 import org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule;
 import org.apache.kafka.common.utils.Utils;
+import org.jose4j.jwk.PublicJsonWebKey;
+import org.jose4j.jwk.RsaJsonWebKey;
+import org.jose4j.jwk.RsaJwkGenerator;
+import org.jose4j.lang.JoseException;
 import org.junit.jupiter.api.TestInstance;
 import org.junit.jupiter.api.TestInstance.Lifecycle;
 import org.junit.jupiter.api.function.Executable;
@@ -195,4 +199,24 @@ public abstract class OAuthBearerTest {
         return getSaslConfigs(Collections.emptyMap());
     }
 
+    protected PublicJsonWebKey createRsaJwk() throws JoseException {
+        RsaJsonWebKey jwk = RsaJwkGenerator.generateJwk(2048);
+        jwk.setKeyId("key-1");
+        return jwk;
+    }
+
+    protected PublicJsonWebKey createEcJwk() throws JoseException {
+        PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk("{" +
+            "  \"kty\": \"EC\"," +
+            "  \"d\": \"Tk7qzHNnSBMioAU7NwZ9JugFWmWbUCyzeBRjVcTp_so\"," +
+            "  \"use\": \"sig\"," +
+            "  \"crv\": \"P-256\"," +
+            "  \"kid\": \"key-1\"," +
+            "  \"x\": \"qqeGjWmYZU5M5bBrRw1zqZcbPunoFVxsfaa9JdA0R5I\"," +
+            "  \"y\": \"wnoj0YjheNP80XYh1SEvz1-wnKByEoHvb6KrDcjMuWc\"" +
+            "}");
+        jwk.setKeyId("key-1");
+        return jwk;
+    }
+
 }
\ No newline at end of file
diff --git a/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/OAuthBearerValidatorCallbackHandlerTest.java b/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/OAuthBearerValidatorCallbackHandlerTest.java
index 326197d..67e2a8b 100644
--- a/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/OAuthBearerValidatorCallbackHandlerTest.java
+++ b/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/OAuthBearerValidatorCallbackHandlerTest.java
@@ -31,6 +31,7 @@ import javax.security.auth.callback.Callback;
 import org.apache.kafka.common.security.oauthbearer.OAuthBearerToken;
 import org.apache.kafka.common.security.oauthbearer.OAuthBearerValidatorCallback;
 import org.apache.kafka.common.utils.Utils;
+import org.jose4j.jws.AlgorithmIdentifiers;
 import org.junit.jupiter.api.Test;
 
 public class OAuthBearerValidatorCallbackHandlerTest extends OAuthBearerTest {
@@ -39,7 +40,10 @@ public class OAuthBearerValidatorCallbackHandlerTest extends OAuthBearerTest {
     public void testBasic() throws Exception {
         String expectedAudience = "a";
         List<String> allAudiences = Arrays.asList(expectedAudience, "b", "c");
-        AccessTokenBuilder builder = new AccessTokenBuilder().audience(expectedAudience);
+        AccessTokenBuilder builder = new AccessTokenBuilder()
+            .audience(expectedAudience)
+            .jwk(createRsaJwk())
+            .alg(AlgorithmIdentifiers.RSA_USING_SHA256);
         String accessToken = builder.build();
 
         Map<String, ?> configs = getSaslConfigs(SASL_OAUTHBEARER_EXPECTED_AUDIENCE, allAudiences);
@@ -92,7 +96,7 @@ public class OAuthBearerValidatorCallbackHandlerTest extends OAuthBearerTest {
         AccessTokenBuilder builder) {
         OAuthBearerValidatorCallbackHandler handler = new OAuthBearerValidatorCallbackHandler();
         CloseableVerificationKeyResolver verificationKeyResolver = (jws, nestingContext) ->
-                builder.jwk().getRsaPublicKey();
+                builder.jwk().getPublicKey();
         AccessTokenValidator accessTokenValidator = AccessTokenValidatorFactory.create(options, verificationKeyResolver);
         handler.init(verificationKeyResolver, accessTokenValidator);
         return handler;
diff --git a/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/ValidatorAccessTokenValidatorTest.java b/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/ValidatorAccessTokenValidatorTest.java
index 76333e3..a481988 100644
--- a/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/ValidatorAccessTokenValidatorTest.java
+++ b/clients/src/test/java/org/apache/kafka/common/security/oauthbearer/secured/ValidatorAccessTokenValidatorTest.java
@@ -19,8 +19,9 @@ package org.apache.kafka.common.security.oauthbearer.secured;
 
 import java.util.Collections;
 import org.apache.kafka.common.security.oauthbearer.OAuthBearerToken;
+import org.jose4j.jwk.PublicJsonWebKey;
 import org.jose4j.jws.AlgorithmIdentifiers;
-import org.jose4j.jws.JsonWebSignature;
+import org.jose4j.lang.InvalidAlgorithmException;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -38,16 +39,30 @@ public class ValidatorAccessTokenValidatorTest extends AccessTokenValidatorTest
     }
 
     @Test
-    public void testBasicEncryption() throws Exception {
-        AccessTokenBuilder builder = new AccessTokenBuilder();
-        AccessTokenValidator validator = createAccessTokenValidator(builder);
+    public void testRsaEncryptionAlgorithm() throws Exception {
+        PublicJsonWebKey jwk = createRsaJwk();
+        testEncryptionAlgorithm(jwk, AlgorithmIdentifiers.RSA_USING_SHA256);
+    }
 
-        JsonWebSignature jws = new JsonWebSignature();
-        jws.setKey(builder.jwk().getPrivateKey());
-        jws.setKeyIdHeaderValue(builder.jwk().getKeyId());
-        jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
-        String accessToken = builder.build();
+    @Test
+    public void testEcdsaEncryptionAlgorithm() throws Exception {
+        PublicJsonWebKey jwk = createEcJwk();
+        testEncryptionAlgorithm(jwk, AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256);
+    }
 
+    @Test
+    public void testInvalidEncryptionAlgorithm() throws Exception {
+        PublicJsonWebKey jwk = createRsaJwk();
+
+        assertThrowsWithMessage(InvalidAlgorithmException.class,
+            () -> testEncryptionAlgorithm(jwk, "fake"),
+            "fake is an unknown, unsupported or unavailable alg algorithm");
+    }
+
+    private void testEncryptionAlgorithm(PublicJsonWebKey jwk, String alg) throws Exception {
+        AccessTokenBuilder builder = new AccessTokenBuilder().jwk(jwk).alg(alg);
+        AccessTokenValidator validator = createAccessTokenValidator(builder);
+        String accessToken = builder.build();
         OAuthBearerToken token = validator.validate(accessToken);
 
         assertEquals(builder.subject(), token.principalName());