You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by co...@apache.org on 2017/09/06 10:03:25 UTC
knox git commit: KNOX-962 - Add signature validation tests for the
JWT filters
Repository: knox
Updated Branches:
refs/heads/master 60900b955 -> c7cbd46a2
KNOX-962 - Add signature validation tests for the JWT filters
Project: http://git-wip-us.apache.org/repos/asf/knox/repo
Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/c7cbd46a
Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/c7cbd46a
Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/c7cbd46a
Branch: refs/heads/master
Commit: c7cbd46a2dcc3cc8a87b948ad5468577ed25651b
Parents: 60900b9
Author: Colm O hEigeartaigh <co...@apache.org>
Authored: Wed Sep 6 11:02:52 2017 +0100
Committer: Colm O hEigeartaigh <co...@apache.org>
Committed: Wed Sep 6 11:02:52 2017 +0100
----------------------------------------------------------------------
.../jwt/filter/AbstractJWTFilter.java | 29 ++---
.../federation/AbstractJWTFilterTest.java | 128 +++++++++++++++----
.../federation/JWTFederationFilterTest.java | 14 +-
.../federation/SSOCookieProviderTest.java | 57 +--------
4 files changed, 133 insertions(+), 95 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/knox/blob/c7cbd46a/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
index 8627b3f..e938480 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
@@ -63,7 +63,6 @@ public abstract class AbstractJWTFilter implements Filter {
static JWTMessages log = MessagesFactory.get( JWTMessages.class );
protected List<String> audiences;
protected JWTokenAuthority authority;
- protected String verificationPEM = null;
protected RSAPublicKey publicKey = null;
private static AuditService auditService = AuditServiceFactory.getAuditService();
private static Auditor auditor = auditService.getAuditor(
@@ -74,7 +73,7 @@ public abstract class AbstractJWTFilter implements Filter {
throws IOException, ServletException;
/**
- *
+ *
*/
public AbstractJWTFilter() {
super();
@@ -128,7 +127,7 @@ public abstract class AbstractJWTFilter implements Filter {
*/
protected boolean validateAudiences(JWTToken jwtToken) {
boolean valid = false;
-
+
String[] tokenAudienceList = jwtToken.getAudienceClaims();
// if there were no expected audiences configured then just
// consider any audience acceptable
@@ -195,18 +194,18 @@ public abstract class AbstractJWTFilter implements Filter {
Set<Principal> principals = new HashSet<>();
Principal p = new PrimaryPrincipal(principal);
principals.add(p);
-
- // The newly constructed Sets check whether this Subject has been set read-only
- // before permitting subsequent modifications. The newly created Sets also prevent
+
+ // The newly constructed Sets check whether this Subject has been set read-only
+ // before permitting subsequent modifications. The newly created Sets also prevent
// illegal modifications by ensuring that callers have sufficient permissions.
//
- // To modify the Principals Set, the caller must have AuthPermission("modifyPrincipals").
- // To modify the public credential Set, the caller must have AuthPermission("modifyPublicCredentials").
+ // To modify the Principals Set, the caller must have AuthPermission("modifyPrincipals").
+ // To modify the public credential Set, the caller must have AuthPermission("modifyPublicCredentials").
// To modify the private credential Set, the caller must have AuthPermission("modifyPrivateCredentials").
javax.security.auth.Subject subject = new javax.security.auth.Subject(true, principals, emptySet, emptySet);
return subject;
}
-
+
protected boolean validateToken(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, JWTToken token)
throws IOException, ServletException {
@@ -221,7 +220,7 @@ public abstract class AbstractJWTFilter implements Filter {
} catch (TokenServiceException e) {
log.unableToVerifyToken(e);
}
-
+
if (verified) {
// confirm that issue matches intended target - which for this filter must be KNOXSSO
if (token.getIssuer().equals("KNOXSSO")) {
@@ -235,13 +234,13 @@ public abstract class AbstractJWTFilter implements Filter {
}
else {
log.failedToValidateAudience();
- handleValidationError(request, response, HttpServletResponse.SC_BAD_REQUEST,
+ handleValidationError(request, response, HttpServletResponse.SC_BAD_REQUEST,
"Bad request: missing required token audience");
}
}
else {
log.tokenHasExpired();
- handleValidationError(request, response, HttpServletResponse.SC_BAD_REQUEST,
+ handleValidationError(request, response, HttpServletResponse.SC_BAD_REQUEST,
"Bad request: token has expired");
}
}
@@ -256,8 +255,8 @@ public abstract class AbstractJWTFilter implements Filter {
return false;
}
-
- protected abstract void handleValidationError(HttpServletRequest request, HttpServletResponse response, int status,
+
+ protected abstract void handleValidationError(HttpServletRequest request, HttpServletResponse response, int status,
String error) throws IOException;
-
+
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/knox/blob/c7cbd46a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/AbstractJWTFilterTest.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/AbstractJWTFilterTest.java b/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/AbstractJWTFilterTest.java
index 471c7d3..1893647 100644
--- a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/AbstractJWTFilterTest.java
+++ b/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/AbstractJWTFilterTest.java
@@ -26,6 +26,7 @@ import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
+import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
@@ -66,7 +67,7 @@ import com.nimbusds.jose.*;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.jose.crypto.RSASSASigner;
-import com.nimbusds.jose.util.Base64URL;
+import com.nimbusds.jose.crypto.RSASSAVerifier;
public abstract class AbstractJWTFilterTest {
private static final String SERVICE_URL = "https://localhost:8888/resource";
@@ -109,7 +110,7 @@ public abstract class AbstractJWTFilterTest {
public void teardown() throws Exception {
handler.destroy();
}
-
+
@Test
public void testValidJWT() throws Exception {
try {
@@ -120,7 +121,7 @@ public abstract class AbstractJWTFilterTest {
HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
setTokenOnRequest(request, jwt);
-
+
EasyMock.expect(request.getRequestURL()).andReturn(
new StringBuffer(SERVICE_URL)).anyTimes();
EasyMock.expect(request.getQueryString()).andReturn(null);
@@ -139,7 +140,7 @@ public abstract class AbstractJWTFilterTest {
fail("Should NOT have thrown a ServletException.");
}
}
-
+
@Test
public void testValidAudienceJWT() throws Exception {
try {
@@ -151,7 +152,7 @@ public abstract class AbstractJWTFilterTest {
HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
setTokenOnRequest(request, jwt);
-
+
EasyMock.expect(request.getRequestURL()).andReturn(
new StringBuffer(SERVICE_URL)).anyTimes();
EasyMock.expect(request.getQueryString()).andReturn(null);
@@ -184,7 +185,7 @@ public abstract class AbstractJWTFilterTest {
HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
setTokenOnRequest(request, jwt);
-
+
EasyMock.expect(request.getRequestURL()).andReturn(
new StringBuffer(SERVICE_URL)).anyTimes();
EasyMock.expect(request.getQueryString()).andReturn(null);
@@ -206,7 +207,7 @@ public abstract class AbstractJWTFilterTest {
public void testValidVerificationPEM() throws Exception {
try {
Properties props = getProperties();
-
+
// System.out.println("+" + pem + "+");
props.put(getAudienceProperty(), "bar");
@@ -248,7 +249,7 @@ public abstract class AbstractJWTFilterTest {
HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
setTokenOnRequest(request, jwt);
-
+
EasyMock.expect(request.getRequestURL()).andReturn(
new StringBuffer(SERVICE_URL)).anyTimes();
EasyMock.expect(request.getQueryString()).andReturn(null);
@@ -265,7 +266,7 @@ public abstract class AbstractJWTFilterTest {
fail("Should NOT have thrown a ServletException.");
}
}
-
+
@Test
public void testValidJWTNoExpiration() throws Exception {
try {
@@ -276,7 +277,7 @@ public abstract class AbstractJWTFilterTest {
HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
setTokenOnRequest(request, jwt);
-
+
EasyMock.expect(request.getRequestURL()).andReturn(
new StringBuffer(SERVICE_URL)).anyTimes();
EasyMock.expect(request.getQueryString()).andReturn(null);
@@ -295,14 +296,14 @@ public abstract class AbstractJWTFilterTest {
fail("Should NOT have thrown a ServletException.");
}
}
-
+
@Test
public void testUnableToParseJWT() throws Exception {
try {
Properties props = getProperties();
handler.init(new TestFilterConfig(props));
- SignedJWT jwt = getJWT("bob",new Date(new Date().getTime() + 5000), privateKey, props);
+ SignedJWT jwt = getJWT("bob", new Date(new Date().getTime() + 5000), privateKey, props);
HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
setGarbledTokenOnRequest(request, jwt);
@@ -317,7 +318,82 @@ public abstract class AbstractJWTFilterTest {
TestFilterChain chain = new TestFilterChain();
handler.doFilter(request, response, chain);
- Assert.assertTrue("doFilterCalled should not be false.", !chain.doFilterCalled);
+ Assert.assertTrue("doFilterCalled should not be true.", !chain.doFilterCalled);
+ Assert.assertTrue("No Subject should be returned.", chain.subject == null);
+ } catch (ServletException se) {
+ fail("Should NOT have thrown a ServletException.");
+ }
+ }
+
+ @Test
+ public void testFailedSignatureValidationJWT() throws Exception {
+ try {
+ // Create a private key to sign the token
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+ kpg.initialize(1024);
+
+ KeyPair kp = kpg.genKeyPair();
+
+ Properties props = getProperties();
+ handler.init(new TestFilterConfig(props));
+
+ SignedJWT jwt = getJWT("bob", new Date(new Date().getTime() + 5000),
+ (RSAPrivateKey)kp.getPrivate(), props);
+
+ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ setTokenOnRequest(request, jwt);
+
+ EasyMock.expect(request.getRequestURL()).andReturn(
+ new StringBuffer(SERVICE_URL)).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null);
+ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(
+ SERVICE_URL).anyTimes();
+ EasyMock.replay(request);
+
+ TestFilterChain chain = new TestFilterChain();
+ handler.doFilter(request, response, chain);
+ Assert.assertTrue("doFilterCalled should not be true.", !chain.doFilterCalled);
+ Assert.assertTrue("No Subject should be returned.", chain.subject == null);
+ } catch (ServletException se) {
+ fail("Should NOT have thrown a ServletException.");
+ }
+ }
+
+ @Test
+ public void testInvalidVerificationPEM() throws Exception {
+ try {
+ Properties props = getProperties();
+
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+ kpg.initialize(1024);
+
+ KeyPair KPair = kpg.generateKeyPair();
+ String dn = buildDistinguishedName(InetAddress.getLocalHost().getHostName());
+ Certificate cert = X509CertificateUtil.generateCertificate(dn, KPair, 365, "SHA1withRSA");
+ byte[] data = cert.getEncoded();
+ Base64 encoder = new Base64( 76, "\n".getBytes( "ASCII" ) );
+ String failingPem = new String(encoder.encodeToString( data ).getBytes( "ASCII" )).trim();
+
+ props.put(getAudienceProperty(), "bar");
+ props.put(getVerificationPemProperty(), failingPem);
+ handler.init(new TestFilterConfig(props));
+
+ SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 50000), privateKey, props);
+
+ HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ setTokenOnRequest(request, jwt);
+
+ EasyMock.expect(request.getRequestURL()).andReturn(
+ new StringBuffer(SERVICE_URL)).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null);
+ HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(SERVICE_URL);
+ EasyMock.replay(request);
+
+ TestFilterChain chain = new TestFilterChain();
+ handler.doFilter(request, response, chain);
+ Assert.assertTrue("doFilterCalled should not be true.", chain.doFilterCalled == false);
Assert.assertTrue("No Subject should be returned.", chain.subject == null);
} catch (ServletException se) {
fail("Should NOT have thrown a ServletException.");
@@ -348,7 +424,6 @@ public abstract class AbstractJWTFilterTest {
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).build();
SignedJWT signedJWT = new SignedJWT(header, claims);
- Base64URL sigInput = Base64URL.encode(signedJWT.getSigningInput());
JWSSigner signer = new RSASSASigner(privateKey);
signedJWT.sign(signer);
@@ -396,11 +471,17 @@ public abstract class AbstractJWTFilterTest {
public Enumeration<String> getInitParameterNames() {
return null;
}
-
+
}
-
+
protected static class TestJWTokenAuthority implements JWTokenAuthority {
+ private PublicKey verifyingKey;
+
+ public TestJWTokenAuthority(PublicKey verifyingKey) {
+ this.verifyingKey = verifyingKey;
+ }
+
/* (non-Javadoc)
* @see org.apache.hadoop.gateway.services.security.token.JWTokenAuthority#issueToken(javax.security.auth.Subject, java.lang.String)
*/
@@ -435,7 +516,8 @@ public abstract class AbstractJWTFilterTest {
*/
@Override
public boolean verifyToken(JWTToken token) throws TokenServiceException {
- return true;
+ JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) verifyingKey);
+ return token.verify(verifier);
}
/* (non-Javadoc)
@@ -465,12 +547,12 @@ public abstract class AbstractJWTFilterTest {
@Override
public boolean verifyToken(JWTToken token, RSAPublicKey publicKey) throws TokenServiceException {
- // TODO Auto-generated method stub
- return true;
+ JWSVerifier verifier = new RSASSAVerifier(publicKey);
+ return token.verify(verifier);
}
-
+
}
-
+
protected static class TestFilterChain implements FilterChain {
boolean doFilterCalled = false;
Subject subject = null;
@@ -482,9 +564,9 @@ public abstract class AbstractJWTFilterTest {
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
doFilterCalled = true;
-
+
subject = Subject.getSubject( AccessController.getContext() );
}
-
+
}
}
http://git-wip-us.apache.org/repos/asf/knox/blob/c7cbd46a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/JWTFederationFilterTest.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/JWTFederationFilterTest.java b/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/JWTFederationFilterTest.java
index 8d41423..d19d999 100644
--- a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/JWTFederationFilterTest.java
+++ b/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/JWTFederationFilterTest.java
@@ -29,19 +29,19 @@ import org.junit.Before;
import com.nimbusds.jwt.SignedJWT;
public class JWTFederationFilterTest extends AbstractJWTFilterTest {
-
+
@Before
public void setup() throws Exception, NoSuchAlgorithmException {
super.setup();
handler = new TestJWTFederationFilter();
- ((TestJWTFederationFilter) handler).setTokenService(new TestJWTokenAuthority());
+ ((TestJWTFederationFilter) handler).setTokenService(new TestJWTokenAuthority(publicKey));
}
-
+
protected void setTokenOnRequest(HttpServletRequest request, SignedJWT jwt) {
String token = "Bearer " + jwt.serialize();
EasyMock.expect(request.getHeader("Authorization")).andReturn(token);
}
-
+
protected void setGarbledTokenOnRequest(HttpServletRequest request, SignedJWT jwt) {
String token = "Bearer " + "ljm" + jwt.serialize();
EasyMock.expect(request.getHeader("Authorization")).andReturn(token);
@@ -50,18 +50,18 @@ public class JWTFederationFilterTest extends AbstractJWTFilterTest {
protected String getAudienceProperty() {
return TestJWTFederationFilter.KNOX_TOKEN_AUDIENCES;
}
-
+
private static class TestJWTFederationFilter extends JWTFederationFilter {
public void setTokenService(JWTokenAuthority ts) {
authority = ts;
}
-
+
}
@Override
protected String getVerificationPemProperty() {
return TestJWTFederationFilter.TOKEN_VERIFICATION_PEM;
};
-
+
}
http://git-wip-us.apache.org/repos/asf/knox/blob/c7cbd46a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java b/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java
index 27a3f31..85f7d59 100644
--- a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java
+++ b/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java
@@ -42,21 +42,19 @@ import com.nimbusds.jwt.SignedJWT;
public class SSOCookieProviderTest extends AbstractJWTFilterTest {
private static final String SERVICE_URL = "https://localhost:8888/resource";
- private static final String REDIRECT_LOCATION =
- "https://localhost:8443/authserver?originalUrl=" + SERVICE_URL;
-
+
@Before
public void setup() throws Exception, NoSuchAlgorithmException {
super.setup();
handler = new TestSSOCookieFederationProvider();
- ((TestSSOCookieFederationProvider) handler).setTokenService(new TestJWTokenAuthority());
+ ((TestSSOCookieFederationProvider) handler).setTokenService(new TestJWTokenAuthority(publicKey));
}
-
+
protected void setTokenOnRequest(HttpServletRequest request, SignedJWT jwt) {
Cookie cookie = new Cookie("hadoop-jwt", jwt.serialize());
EasyMock.expect(request.getCookies()).andReturn(new Cookie[] { cookie });
}
-
+
protected void setGarbledTokenOnRequest(HttpServletRequest request, SignedJWT jwt) {
Cookie cookie = new Cookie("hadoop-jwt", "ljm" + jwt.serialize());
EasyMock.expect(request.getCookies()).andReturn(new Cookie[] { cookie });
@@ -65,7 +63,7 @@ public class SSOCookieProviderTest extends AbstractJWTFilterTest {
protected String getAudienceProperty() {
return TestSSOCookieFederationProvider.SSO_EXPECTED_AUDIENCES;
}
-
+
@Test
public void testCustomCookieNameJWT() throws Exception {
try {
@@ -112,47 +110,6 @@ public class SSOCookieProviderTest extends AbstractJWTFilterTest {
se.getMessage().contains("authentication provider URL is missing");
}
}
-
-/*
- @Test
- public void testFailedSignatureValidationJWT() throws Exception {
- try {
-
- // Create a public key that doesn't match the one needed to
- // verify the signature - in order to make it fail verification...
- KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
- kpg.initialize(2048);
-
- KeyPair kp = kpg.genKeyPair();
- RSAPublicKey publicKey = (RSAPublicKey) kp.getPublic();
-
- handler.setPublicKey(publicKey);
-
- Properties props = getProperties();
- handler.init(props);
-
- SignedJWT jwt = getJWT("bob", new Date(new Date().getTime() + 5000),
- privateKey);
-
- Cookie cookie = new Cookie("hadoop-jwt", jwt.serialize());
- HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
- Mockito.when(request.getCookies()).thenReturn(new Cookie[] { cookie });
- Mockito.when(request.getRequestURL()).thenReturn(
- new StringBuffer(SERVICE_URL)).anyTimes();
- HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
- Mockito.when(response.encodeRedirectURL(SERVICE_URL)).thenReturn(
- SERVICE_URL);
-
- AuthenticationToken token = handler.alternateAuthenticate(request,
- response);
- Mockito.verify(response).sendRedirect(REDIRECT_LOCATION);
- } catch (ServletException se) {
- fail("alternateAuthentication should NOT have thrown a ServletException");
- } catch (AuthenticationException ae) {
- fail("alternateAuthentication should NOT have thrown a AuthenticationException");
- }
- }
-*/
@Test
public void testOrigURLWithQueryString() throws Exception {
@@ -185,7 +142,7 @@ public class SSOCookieProviderTest extends AbstractJWTFilterTest {
Assert.assertNotNull("LoginURL should not be null.", loginURL);
Assert.assertEquals("https://localhost:8443/authserver?originalUrl=" + SERVICE_URL, loginURL);
}
-
+
@Override
protected String getVerificationPemProperty() {
@@ -196,7 +153,7 @@ public class SSOCookieProviderTest extends AbstractJWTFilterTest {
public String testConstructLoginURL(HttpServletRequest req) {
return constructLoginURL(req);
}
-
+
public void setTokenService(JWTokenAuthority ts) {
authority = ts;
}