KNOX-1067 - Support different signature algorithms for JWTs
Project: http://git-wip-us.apache.org/repos/asf/knox/repo Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/9c7aa7e1 Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/9c7aa7e1 Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/9c7aa7e1 Branch: refs/heads/KNOX-998-Package_Restructuring Commit: 9c7aa7e1c7471f71c783681b68beea8e6f3fc2dc Parents: 6acfa43 Author: Colm O hEigeartaigh <cohei...@apache.org> Authored: Mon Oct 16 12:26:28 2017 +0100 Committer: Colm O hEigeartaigh <cohei...@apache.org> Committed: Mon Oct 16 15:41:08 2017 +0100 ---------------------------------------------------------------------- .../jwt/filter/AbstractJWTFilter.java | 45 ++++++-- .../jwt/filter/JWTFederationFilter.java | 5 +- .../jwt/filter/SSOCookieFederationFilter.java | 5 +- .../federation/AbstractJWTFilterTest.java | 102 ++++++++++++++++--- .../federation/SSOCookieProviderTest.java | 5 +- .../impl/DefaultTokenAuthorityService.java | 22 +++- .../impl/DefaultTokenAuthorityServiceTest.java | 93 +++++++++++++++++ .../gateway/service/knoxsso/WebSSOResource.java | 11 +- .../service/knoxsso/WebSSOResourceTest.java | 69 +++++++++++-- .../service/knoxtoken/TokenResource.java | 11 +- .../knoxtoken/TokenServiceResourceTest.java | 76 +++++++++++--- .../services/security/token/impl/JWTToken.java | 3 - .../security/token/impl/JWTTokenTest.java | 45 +++++--- 13 files changed, 412 insertions(+), 80 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/knox/blob/9c7aa7e1/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 7f8e733..deb3d5b 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 @@ -22,6 +22,7 @@ import java.security.Principal; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.security.interfaces.RSAPublicKey; +import java.text.ParseException; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; @@ -54,7 +55,9 @@ import org.apache.hadoop.gateway.security.PrimaryPrincipal; import org.apache.hadoop.gateway.services.GatewayServices; import org.apache.hadoop.gateway.services.security.token.JWTokenAuthority; import org.apache.hadoop.gateway.services.security.token.TokenServiceException; -import org.apache.hadoop.gateway.services.security.token.impl.JWTToken; +import org.apache.hadoop.gateway.services.security.token.impl.JWT; + +import com.nimbusds.jose.JWSHeader; /** * @@ -67,6 +70,13 @@ public abstract class AbstractJWTFilter implements Filter { public static final String JWT_EXPECTED_ISSUER = "jwt.expected.issuer"; public static final String JWT_DEFAULT_ISSUER = "KNOXSSO"; + /** + * If specified, this configuration property refers to the signature algorithm which a received + * token must match. Otherwise, the default value "RS256" is used + */ + public static final String JWT_EXPECTED_SIGALG = "jwt.expected.sigalg"; + public static final String JWT_DEFAULT_SIGALG = "RS256"; + static JWTMessages log = MessagesFactory.get( JWTMessages.class ); private static AuditService auditService = AuditServiceFactory.getAuditService(); private static Auditor auditor = auditService.getAuditor( @@ -77,6 +87,7 @@ public abstract class AbstractJWTFilter implements Filter { protected JWTokenAuthority authority; protected RSAPublicKey publicKey = null; private String expectedIssuer; + private String expectedSigAlg; public abstract void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; @@ -99,11 +110,16 @@ public abstract class AbstractJWTFilter implements Filter { } } - protected void configureExpectedIssuer(FilterConfig filterConfig) { - expectedIssuer = filterConfig.getInitParameter(JWT_EXPECTED_ISSUER);; + protected void configureExpectedParameters(FilterConfig filterConfig) { + expectedIssuer = filterConfig.getInitParameter(JWT_EXPECTED_ISSUER); if (expectedIssuer == null) { expectedIssuer = JWT_DEFAULT_ISSUER; } + + expectedSigAlg = filterConfig.getInitParameter(JWT_EXPECTED_SIGALG); + if (expectedSigAlg == null) { + expectedSigAlg = JWT_DEFAULT_SIGALG; + } } /** @@ -111,7 +127,7 @@ public abstract class AbstractJWTFilter implements Filter { * @return */ protected List<String> parseExpectedAudiences(String expectedAudiences) { - ArrayList<String> audList = null; + List<String> audList = null; // setup the list of valid audiences for token validation if (expectedAudiences != null) { // parse into the list @@ -124,7 +140,7 @@ public abstract class AbstractJWTFilter implements Filter { return audList; } - protected boolean tokenIsStillValid(JWTToken jwtToken) { + protected boolean tokenIsStillValid(JWT jwtToken) { // if there is no expiration date then the lifecycle is tied entirely to // the cookie validity - otherwise ensure that the current time is before // the designated expiration time @@ -141,7 +157,7 @@ public abstract class AbstractJWTFilter implements Filter { * the JWT token where the allowed audiences will be found * @return true if an expected audience is present, otherwise false */ - protected boolean validateAudiences(JWTToken jwtToken) { + protected boolean validateAudiences(JWT jwtToken) { boolean valid = false; String[] tokenAudienceList = jwtToken.getAudienceClaims(); @@ -202,7 +218,7 @@ public abstract class AbstractJWTFilter implements Filter { } } - protected Subject createSubjectFromToken(JWTToken token) { + protected Subject createSubjectFromToken(JWT token) { final String principal = token.getSubject(); @SuppressWarnings("rawtypes") @@ -223,7 +239,7 @@ public abstract class AbstractJWTFilter implements Filter { } protected boolean validateToken(HttpServletRequest request, HttpServletResponse response, - FilterChain chain, JWTToken token) + FilterChain chain, JWT token) throws IOException, ServletException { boolean verified = false; try { @@ -237,6 +253,19 @@ public abstract class AbstractJWTFilter implements Filter { log.unableToVerifyToken(e); } + // Check received signature algorithm + if (verified) { + try { + String receivedSigAlg = JWSHeader.parse(token.getHeader()).getAlgorithm().getName(); + if (!receivedSigAlg.equals(expectedSigAlg)) { + verified = false; + } + } catch (ParseException e) { + log.unableToVerifyToken(e); + verified = false; + } + } + if (verified) { // confirm that issue matches intended target if (expectedIssuer.equals(token.getIssuer())) { http://git-wip-us.apache.org/repos/asf/knox/blob/9c7aa7e1/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/JWTFederationFilter.java ---------------------------------------------------------------------- diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/JWTFederationFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/JWTFederationFilter.java index 401e449..dcc52c0 100644 --- a/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/JWTFederationFilter.java +++ b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/JWTFederationFilter.java @@ -17,6 +17,7 @@ */ package org.apache.hadoop.gateway.provider.federation.jwt.filter; +import org.apache.hadoop.gateway.services.security.token.impl.JWT; import org.apache.hadoop.gateway.services.security.token.impl.JWTToken; import org.apache.hadoop.gateway.util.CertificateUtils; @@ -63,7 +64,7 @@ public class JWTFederationFilter extends AbstractJWTFilter { publicKey = CertificateUtils.parseRSAPublicKey(verificationPEM); } - configureExpectedIssuer(filterConfig); + configureExpectedParameters(filterConfig); } public void destroy() { @@ -84,7 +85,7 @@ public class JWTFederationFilter extends AbstractJWTFilter { if (wireToken != null) { try { - JWTToken token = new JWTToken(wireToken); + JWT token = new JWTToken(wireToken); if (validateToken((HttpServletRequest)request, (HttpServletResponse)response, chain, token)) { Subject subject = createSubjectFromToken(token); continueWithEstablishedSecurityContext(subject, (HttpServletRequest)request, (HttpServletResponse)response, chain); http://git-wip-us.apache.org/repos/asf/knox/blob/9c7aa7e1/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java ---------------------------------------------------------------------- diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java index cf14863..7e1c64a 100644 --- a/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java +++ b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java @@ -33,6 +33,7 @@ import javax.servlet.http.HttpServletResponse; import org.apache.hadoop.gateway.i18n.messages.MessagesFactory; import org.apache.hadoop.gateway.provider.federation.jwt.JWTMessages; import org.apache.hadoop.gateway.security.PrimaryPrincipal; +import org.apache.hadoop.gateway.services.security.token.impl.JWT; import org.apache.hadoop.gateway.services.security.token.impl.JWTToken; import org.apache.hadoop.gateway.util.CertificateUtils; @@ -78,7 +79,7 @@ public class SSOCookieFederationFilter extends AbstractJWTFilter { publicKey = CertificateUtils.parseRSAPublicKey(verificationPEM); } - configureExpectedIssuer(filterConfig); + configureExpectedParameters(filterConfig); } public void destroy() { @@ -105,7 +106,7 @@ public class SSOCookieFederationFilter extends AbstractJWTFilter { } else { try { - JWTToken token = new JWTToken(wireToken); + JWT token = new JWTToken(wireToken); if (validateToken((HttpServletRequest)request, (HttpServletResponse)response, chain, token)) { Subject subject = createSubjectFromToken(token); continueWithEstablishedSecurityContext(subject, (HttpServletRequest)request, (HttpServletResponse)response, chain); http://git-wip-us.apache.org/repos/asf/knox/blob/9c7aa7e1/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 bd34c04..b261081 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 @@ -116,7 +116,8 @@ public abstract class AbstractJWTFilterTest { Properties props = getProperties(); handler.init(new TestFilterConfig(props)); - SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 5000), privateKey, props); + SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice", + new Date(new Date().getTime() + 5000), privateKey); HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); setTokenOnRequest(request, jwt); @@ -147,7 +148,8 @@ public abstract class AbstractJWTFilterTest { props.put(getAudienceProperty(), "bar"); handler.init(new TestFilterConfig(props)); - SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 5000), privateKey, props); + SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice", + new Date(new Date().getTime() + 5000), privateKey); HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); setTokenOnRequest(request, jwt); @@ -180,7 +182,8 @@ public abstract class AbstractJWTFilterTest { handler.init(new TestFilterConfig(props)); - SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 5000), privateKey, props); + SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice", + new Date(new Date().getTime() + 5000), privateKey); HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); setTokenOnRequest(request, jwt); @@ -209,7 +212,8 @@ public abstract class AbstractJWTFilterTest { props.put(getAudienceProperty(), " foo, bar "); handler.init(new TestFilterConfig(props)); - SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 5000), privateKey, props); + SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice", + new Date(new Date().getTime() + 5000), privateKey); HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); setTokenOnRequest(request, jwt); @@ -245,7 +249,8 @@ public abstract class AbstractJWTFilterTest { props.put(getVerificationPemProperty(), pem); handler.init(new TestFilterConfig(props)); - SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 50000), privateKey, props); + SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice", + new Date(new Date().getTime() + 50000), privateKey); HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); setTokenOnRequest(request, jwt); @@ -275,7 +280,8 @@ public abstract class AbstractJWTFilterTest { Properties props = getProperties(); handler.init(new TestFilterConfig(props)); - SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() - 1000), privateKey, props); + SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice", + new Date(new Date().getTime() - 1000), privateKey); HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); setTokenOnRequest(request, jwt); @@ -303,7 +309,7 @@ public abstract class AbstractJWTFilterTest { Properties props = getProperties(); handler.init(new TestFilterConfig(props)); - SignedJWT jwt = getJWT("alice", null, privateKey, props); + SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice", null, privateKey); HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); setTokenOnRequest(request, jwt); @@ -333,7 +339,8 @@ public abstract class AbstractJWTFilterTest { Properties props = getProperties(); handler.init(new TestFilterConfig(props)); - SignedJWT jwt = getJWT("bob", new Date(new Date().getTime() + 5000), privateKey, props); + SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "bob", + new Date(new Date().getTime() + 5000), privateKey); HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); setGarbledTokenOnRequest(request, jwt); @@ -367,8 +374,8 @@ public abstract class AbstractJWTFilterTest { Properties props = getProperties(); handler.init(new TestFilterConfig(props)); - SignedJWT jwt = getJWT("bob", new Date(new Date().getTime() + 5000), - (RSAPrivateKey)kp.getPrivate(), props); + SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "bob", + new Date(new Date().getTime() + 5000), (RSAPrivateKey)kp.getPrivate()); HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); setTokenOnRequest(request, jwt); @@ -409,7 +416,8 @@ public abstract class AbstractJWTFilterTest { props.put(getVerificationPemProperty(), failingPem); handler.init(new TestFilterConfig(props)); - SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 50000), privateKey, props); + SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice", + new Date(new Date().getTime() + 50000), privateKey); HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); setTokenOnRequest(request, jwt); @@ -489,6 +497,67 @@ public abstract class AbstractJWTFilterTest { } } + @Test + public void testRS512SignatureAlgorithm() throws Exception { + try { + Properties props = getProperties(); + props.put(AbstractJWTFilter.JWT_EXPECTED_SIGALG, "RS512"); + handler.init(new TestFilterConfig(props)); + + SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice", new Date(new Date().getTime() + 5000), + privateKey, JWSAlgorithm.RS512.getName()); + + 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 false.", chain.doFilterCalled ); + Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class); + Assert.assertTrue("No PrimaryPrincipal", !principals.isEmpty()); + Assert.assertEquals("Not the expected principal", "alice", ((Principal)principals.toArray()[0]).getName()); + } catch (ServletException se) { + fail("Should NOT have thrown a ServletException."); + } + } + + @Test + public void testInvalidSignatureAlgorithm() throws Exception { + try { + Properties props = getProperties(); + handler.init(new TestFilterConfig(props)); + + SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice", new Date(new Date().getTime() + 5000), + privateKey, JWSAlgorithm.RS384.getName()); + + 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 false.", !chain.doFilterCalled ); + Assert.assertTrue("No Subject should be returned.", chain.subject == null); + } catch (ServletException se) { + fail("Should NOT have thrown a ServletException."); + } + } + protected Properties getProperties() { Properties props = new Properties(); props.setProperty( @@ -497,12 +566,13 @@ public abstract class AbstractJWTFilterTest { return props; } - protected SignedJWT getJWT(String sub, Date expires, RSAPrivateKey privateKey, - Properties props) throws Exception { - return getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, sub, expires, privateKey); + protected SignedJWT getJWT(String issuer, String sub, Date expires, RSAPrivateKey privateKey) + throws Exception { + return getJWT(issuer, sub, expires, privateKey, JWSAlgorithm.RS256.getName()); } - protected SignedJWT getJWT(String issuer, String sub, Date expires, RSAPrivateKey privateKey) + protected SignedJWT getJWT(String issuer, String sub, Date expires, RSAPrivateKey privateKey, + String signatureAlgorithm) throws Exception { List<String> aud = new ArrayList<String>(); aud.add("bar"); @@ -515,7 +585,7 @@ public abstract class AbstractJWTFilterTest { .claim("scope", "openid") .build(); - JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).build(); + JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.parse(signatureAlgorithm)).build(); SignedJWT signedJWT = new SignedJWT(header, claims); JWSSigner signer = new RSASSASigner(privateKey); http://git-wip-us.apache.org/repos/asf/knox/blob/9c7aa7e1/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 768755b..38e7381 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 @@ -30,6 +30,7 @@ import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.hadoop.gateway.provider.federation.jwt.filter.AbstractJWTFilter; import org.apache.hadoop.gateway.provider.federation.jwt.filter.SSOCookieFederationFilter; import org.apache.hadoop.gateway.security.PrimaryPrincipal; import org.apache.hadoop.gateway.services.security.token.JWTokenAuthority; @@ -70,8 +71,8 @@ public class SSOCookieProviderTest extends AbstractJWTFilterTest { props.put("sso.cookie.name", "jowt"); handler.init(new TestFilterConfig(props)); - SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 5000), - privateKey, props); + SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice", + new Date(new Date().getTime() + 5000), privateKey); Cookie cookie = new Cookie("jowt", jwt.serialize()); HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); http://git-wip-us.apache.org/repos/asf/knox/blob/9c7aa7e1/gateway-server/src/main/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityService.java ---------------------------------------------------------------------- diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityService.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityService.java index 33b86bd..0c33cdf 100644 --- a/gateway-server/src/main/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityService.java +++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityService.java @@ -23,8 +23,10 @@ import java.security.PublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.util.Map; +import java.util.Set; import java.util.List; import java.util.ArrayList; +import java.util.HashSet; import javax.security.auth.Subject; @@ -48,10 +50,22 @@ import com.nimbusds.jose.crypto.RSASSAVerifier; public class DefaultTokenAuthorityService implements JWTokenAuthority, Service { private static final String SIGNING_KEY_PASSPHRASE = "signing.key.passphrase"; + private static final Set<String> SUPPORTED_SIG_ALGS = new HashSet<>(); private AliasService as = null; private KeystoreService ks = null; String signingKeyAlias = null; + static { + // Only standard RSA signature algorithms are accepted + // https://tools.ietf.org/html/rfc7518 + SUPPORTED_SIG_ALGS.add("RS256"); + SUPPORTED_SIG_ALGS.add("RS384"); + SUPPORTED_SIG_ALGS.add("RS512"); + SUPPORTED_SIG_ALGS.add("PS256"); + SUPPORTED_SIG_ALGS.add("PS384"); + SUPPORTED_SIG_ALGS.add("PS512"); + } + public void setKeystoreService(KeystoreService ks) { this.ks = ks; } @@ -96,7 +110,7 @@ public class DefaultTokenAuthorityService implements JWTokenAuthority, Service { @Override public JWT issueToken(Principal p, String audience, String algorithm, long expires) throws TokenServiceException { - ArrayList<String> audiences = null; + List<String> audiences = null; if (audience != null) { audiences = new ArrayList<String>(); audiences.add(audience); @@ -118,9 +132,9 @@ public class DefaultTokenAuthorityService implements JWTokenAuthority, Service { claimArray[3] = String.valueOf(expires); } - JWTToken token = null; - if ("RS256".equals(algorithm)) { - token = new JWTToken("RS256", claimArray, audiences); + JWT token = null; + if (SUPPORTED_SIG_ALGS.contains(algorithm)) { + token = new JWTToken(algorithm, claimArray, audiences); RSAPrivateKey key; char[] passphrase = null; try { http://git-wip-us.apache.org/repos/asf/knox/blob/9c7aa7e1/gateway-server/src/test/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityServiceTest.java ---------------------------------------------------------------------- diff --git a/gateway-server/src/test/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityServiceTest.java b/gateway-server/src/test/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityServiceTest.java index 7cc9971..48616c0 100644 --- a/gateway-server/src/test/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityServiceTest.java +++ b/gateway-server/src/test/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityServiceTest.java @@ -27,6 +27,7 @@ import org.apache.hadoop.gateway.services.security.KeystoreService; import org.apache.hadoop.gateway.services.security.MasterService; import org.apache.hadoop.gateway.services.security.impl.DefaultKeystoreService; import org.apache.hadoop.gateway.services.security.token.JWTokenAuthority; +import org.apache.hadoop.gateway.services.security.token.TokenServiceException; import org.apache.hadoop.gateway.services.security.token.impl.JWT; import org.easymock.EasyMock; import org.junit.Test; @@ -74,6 +75,8 @@ public class DefaultTokenAuthorityServiceTest extends org.junit.Assert { JWT token = ta.issueToken(principal, "RS256"); assertEquals("KNOXSSO", token.getIssuer()); assertEquals("john....@example.com", token.getSubject()); + + assertTrue(ta.verifyToken(token)); } @Test @@ -115,6 +118,8 @@ public class DefaultTokenAuthorityServiceTest extends org.junit.Assert { assertEquals("KNOXSSO", token.getIssuer()); assertEquals("john....@example.com", token.getSubject()); assertEquals("https://login.example.com", token.getAudience()); + + assertTrue(ta.verifyToken(token)); } @Test @@ -155,6 +160,94 @@ public class DefaultTokenAuthorityServiceTest extends org.junit.Assert { JWT token = ta.issueToken(principal, null, "RS256"); assertEquals("KNOXSSO", token.getIssuer()); assertEquals("john....@example.com", token.getSubject()); + + assertTrue(ta.verifyToken(token)); + } + + @Test + public void testTokenCreationSignatureAlgorithm() throws Exception { + + Principal principal = EasyMock.createNiceMock(Principal.class); + EasyMock.expect(principal.getName()).andReturn("john....@example.com"); + + GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class); + String basedir = System.getProperty("basedir"); + if (basedir == null) { + basedir = new File(".").getCanonicalPath(); + } + + EasyMock.expect(config.getGatewaySecurityDir()).andReturn(basedir + "/target/test-classes"); + EasyMock.expect(config.getSigningKeystoreName()).andReturn("server-keystore.jks"); + EasyMock.expect(config.getSigningKeyAlias()).andReturn("server").anyTimes(); + + MasterService ms = EasyMock.createNiceMock(MasterService.class); + EasyMock.expect(ms.getMasterSecret()).andReturn("horton".toCharArray()); + + AliasService as = EasyMock.createNiceMock(AliasService.class); + EasyMock.expect(as.getGatewayIdentityPassphrase()).andReturn("horton".toCharArray()); + + EasyMock.replay(principal, config, ms, as); + + KeystoreService ks = new DefaultKeystoreService(); + ((DefaultKeystoreService)ks).setMasterService(ms); + + ((DefaultKeystoreService)ks).init(config, new HashMap<String, String>()); + + JWTokenAuthority ta = new DefaultTokenAuthorityService(); + ((DefaultTokenAuthorityService)ta).setAliasService(as); + ((DefaultTokenAuthorityService)ta).setKeystoreService(ks); + + ((DefaultTokenAuthorityService)ta).init(config, new HashMap<String, String>()); + + JWT token = ta.issueToken(principal, "RS512"); + assertEquals("KNOXSSO", token.getIssuer()); + assertEquals("john....@example.com", token.getSubject()); + assertTrue(token.getHeader().contains("RS512")); + + assertTrue(ta.verifyToken(token)); + } + + @Test + public void testTokenCreationBadSignatureAlgorithm() throws Exception { + + Principal principal = EasyMock.createNiceMock(Principal.class); + EasyMock.expect(principal.getName()).andReturn("john....@example.com"); + + GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class); + String basedir = System.getProperty("basedir"); + if (basedir == null) { + basedir = new File(".").getCanonicalPath(); + } + + EasyMock.expect(config.getGatewaySecurityDir()).andReturn(basedir + "/target/test-classes"); + EasyMock.expect(config.getSigningKeystoreName()).andReturn("server-keystore.jks"); + EasyMock.expect(config.getSigningKeyAlias()).andReturn("server").anyTimes(); + + MasterService ms = EasyMock.createNiceMock(MasterService.class); + EasyMock.expect(ms.getMasterSecret()).andReturn("horton".toCharArray()); + + AliasService as = EasyMock.createNiceMock(AliasService.class); + EasyMock.expect(as.getGatewayIdentityPassphrase()).andReturn("horton".toCharArray()); + + EasyMock.replay(principal, config, ms, as); + + KeystoreService ks = new DefaultKeystoreService(); + ((DefaultKeystoreService)ks).setMasterService(ms); + + ((DefaultKeystoreService)ks).init(config, new HashMap<String, String>()); + + JWTokenAuthority ta = new DefaultTokenAuthorityService(); + ((DefaultTokenAuthorityService)ta).setAliasService(as); + ((DefaultTokenAuthorityService)ta).setKeystoreService(ks); + + ((DefaultTokenAuthorityService)ta).init(config, new HashMap<String, String>()); + + try { + ta.issueToken(principal, "none"); + fail("Failure expected on a bad signature algorithm"); + } catch (TokenServiceException ex) { + // expected + } } } http://git-wip-us.apache.org/repos/asf/knox/blob/9c7aa7e1/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java ---------------------------------------------------------------------- diff --git a/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java b/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java index 70228d3..36aa075 100644 --- a/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java +++ b/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java @@ -60,6 +60,7 @@ public class WebSSOResource { private static final String SSO_COOKIE_DOMAIN_SUFFIX_PARAM = "knoxsso.cookie.domain.suffix"; private static final String SSO_COOKIE_TOKEN_TTL_PARAM = "knoxsso.token.ttl"; private static final String SSO_COOKIE_TOKEN_AUDIENCES_PARAM = "knoxsso.token.audiences"; + private static final String SSO_COOKIE_TOKEN_SIG_ALG = "knoxsso.token.sigalg"; private static final String SSO_COOKIE_TOKEN_WHITELIST_PARAM = "knoxsso.redirect.whitelist.regex"; private static final String SSO_ENABLE_SESSION_PARAM = "knoxsso.enable.session"; private static final String ORIGINAL_URL_REQUEST_PARAM = "originalUrl"; @@ -77,6 +78,7 @@ public class WebSSOResource { private String domainSuffix = null; private List<String> targetAudiences = new ArrayList<>(); private boolean enableSession = false; + private String signatureAlgorithm = "RS256"; @Context HttpServletRequest request; @@ -143,6 +145,11 @@ public class WebSSOResource { String enableSession = context.getInitParameter(SSO_ENABLE_SESSION_PARAM); this.enableSession = ("true".equals(enableSession)); + + String sigAlg = context.getInitParameter(SSO_COOKIE_TOKEN_SIG_ALG); + if (sigAlg != null) { + signatureAlgorithm = sigAlg; + } } @GET @@ -185,9 +192,9 @@ public class WebSSOResource { try { JWT token = null; if (targetAudiences.isEmpty()) { - token = ts.issueToken(p, "RS256", getExpiry()); + token = ts.issueToken(p, signatureAlgorithm, getExpiry()); } else { - token = ts.issueToken(p, targetAudiences, "RS256", getExpiry()); + token = ts.issueToken(p, targetAudiences, signatureAlgorithm, getExpiry()); } // Coverity CID 1327959 http://git-wip-us.apache.org/repos/asf/knox/blob/9c7aa7e1/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java ---------------------------------------------------------------------- diff --git a/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java b/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java index 568f0fe..516f9ae 100644 --- a/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java +++ b/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java @@ -166,7 +166,7 @@ public class WebSSOResourceTest { Cookie cookie = responseWrapper.getCookie("hadoop-jwt"); assertNotNull(cookie); - JWTToken parsedToken = new JWTToken(cookie.getValue()); + JWT parsedToken = new JWTToken(cookie.getValue()); assertEquals("alice", parsedToken.getSubject()); assertTrue(authority.verifyToken(parsedToken)); } @@ -218,7 +218,7 @@ public class WebSSOResourceTest { Cookie cookie = responseWrapper.getCookie("hadoop-jwt"); assertNotNull(cookie); - JWTToken parsedToken = new JWTToken(cookie.getValue()); + JWT parsedToken = new JWTToken(cookie.getValue()); assertEquals("alice", parsedToken.getSubject()); assertTrue(authority.verifyToken(parsedToken)); @@ -287,6 +287,60 @@ public class WebSSOResourceTest { assertTrue(audiences.contains("recipient2")); } + @Test + public void testSignatureAlgorithm() throws Exception { + + ServletContext context = EasyMock.createNiceMock(ServletContext.class); + EasyMock.expect(context.getInitParameter("knoxsso.cookie.name")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.cookie.secure.only")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.cookie.max.age")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.cookie.domain.suffix")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.redirect.whitelist.regex")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.token.audiences")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.token.ttl")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.enable.session")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.token.sigalg")).andReturn("RS512"); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + EasyMock.expect(request.getParameter("originalUrl")).andReturn("http://localhost:9080/service"); + EasyMock.expect(request.getParameterMap()).andReturn(Collections.<String,String[]>emptyMap()); + EasyMock.expect(request.getServletContext()).andReturn(context).anyTimes(); + + Principal principal = EasyMock.createNiceMock(Principal.class); + EasyMock.expect(principal.getName()).andReturn("alice").anyTimes(); + EasyMock.expect(request.getUserPrincipal()).andReturn(principal).anyTimes(); + + GatewayServices services = EasyMock.createNiceMock(GatewayServices.class); + EasyMock.expect(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).andReturn(services); + + JWTokenAuthority authority = new TestJWTokenAuthority(publicKey, privateKey); + EasyMock.expect(services.getService(GatewayServices.TOKEN_SERVICE)).andReturn(authority); + + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + ServletOutputStream outputStream = EasyMock.createNiceMock(ServletOutputStream.class); + CookieResponseWrapper responseWrapper = new CookieResponseWrapper(response, outputStream); + + EasyMock.replay(principal, services, context, request); + + WebSSOResource webSSOResponse = new WebSSOResource(); + webSSOResponse.request = request; + webSSOResponse.response = responseWrapper; + webSSOResponse.context = context; + webSSOResponse.init(); + + // Issue a token + webSSOResponse.doGet(); + + // Check the cookie + Cookie cookie = responseWrapper.getCookie("hadoop-jwt"); + assertNotNull(cookie); + + JWT parsedToken = new JWTToken(cookie.getValue()); + assertEquals("alice", parsedToken.getSubject()); + assertTrue(authority.verifyToken(parsedToken)); + assertTrue(parsedToken.getHeader().contains("RS512")); + } + /** * A wrapper for HttpServletResponseWrapper to store the cookies */ @@ -380,14 +434,9 @@ public class WebSSOResourceTest { claimArray[3] = String.valueOf(expires); } - JWTToken token = null; - if ("RS256".equals(algorithm)) { - token = new JWTToken("RS256", claimArray, audiences); - JWSSigner signer = new RSASSASigner(privateKey); - token.sign(signer); - } else { - throw new TokenServiceException("Cannot issue token - Unsupported algorithm"); - } + JWT token = new JWTToken(algorithm, claimArray, audiences); + JWSSigner signer = new RSASSASigner(privateKey); + token.sign(signer); return token; } http://git-wip-us.apache.org/repos/asf/knox/blob/9c7aa7e1/gateway-service-knoxtoken/src/main/java/org/apache/hadoop/gateway/service/knoxtoken/TokenResource.java ---------------------------------------------------------------------- diff --git a/gateway-service-knoxtoken/src/main/java/org/apache/hadoop/gateway/service/knoxtoken/TokenResource.java b/gateway-service-knoxtoken/src/main/java/org/apache/hadoop/gateway/service/knoxtoken/TokenResource.java index df8288a..afa6a3a 100644 --- a/gateway-service-knoxtoken/src/main/java/org/apache/hadoop/gateway/service/knoxtoken/TokenResource.java +++ b/gateway-service-knoxtoken/src/main/java/org/apache/hadoop/gateway/service/knoxtoken/TokenResource.java @@ -57,6 +57,7 @@ public class TokenResource { private static final String TOKEN_CLIENT_DATA = "knox.token.client.data"; private static final String TOKEN_CLIENT_CERT_REQUIRED = "knox.token.client.cert.required"; private static final String TOKEN_ALLOWED_PRINCIPALS = "knox.token.allowed.principals"; + private static final String TOKEN_SIG_ALG = "knox.token.sigalg"; static final String RESOURCE_PATH = "knoxtoken/api/v1/token"; private static TokenServiceMessages log = MessagesFactory.get( TokenServiceMessages.class ); private long tokenTTL = 30000l; @@ -65,6 +66,7 @@ public class TokenResource { private Map<String,Object> tokenClientDataMap = null; private ArrayList<String> allowedDNs = new ArrayList<>(); private boolean clientCertRequired = false; + private String signatureAlgorithm = "RS256"; @Context HttpServletRequest request; @@ -115,6 +117,11 @@ public class TokenResource { String[] tokenClientData = clientData.split(","); addClientDataToMap(tokenClientData, tokenClientDataMap); } + + String sigAlg = context.getInitParameter(TOKEN_SIG_ALG); + if (sigAlg != null) { + signatureAlgorithm = sigAlg; + } } @GET @@ -159,9 +166,9 @@ public class TokenResource { try { JWT token = null; if (targetAudiences.isEmpty()) { - token = ts.issueToken(p, "RS256", expires); + token = ts.issueToken(p, signatureAlgorithm, expires); } else { - token = ts.issueToken(p, targetAudiences, "RS256", expires); + token = ts.issueToken(p, targetAudiences, signatureAlgorithm, expires); } if (token != null) { http://git-wip-us.apache.org/repos/asf/knox/blob/9c7aa7e1/gateway-service-knoxtoken/src/test/java/org/apache/hadoop/gateway/service/knoxtoken/TokenServiceResourceTest.java ---------------------------------------------------------------------- diff --git a/gateway-service-knoxtoken/src/test/java/org/apache/hadoop/gateway/service/knoxtoken/TokenServiceResourceTest.java b/gateway-service-knoxtoken/src/test/java/org/apache/hadoop/gateway/service/knoxtoken/TokenServiceResourceTest.java index 0046bd9..80f359d 100644 --- a/gateway-service-knoxtoken/src/test/java/org/apache/hadoop/gateway/service/knoxtoken/TokenServiceResourceTest.java +++ b/gateway-service-knoxtoken/src/test/java/org/apache/hadoop/gateway/service/knoxtoken/TokenServiceResourceTest.java @@ -101,11 +101,8 @@ public class TokenServiceResourceTest { @Test public void testGetToken() throws Exception { - TokenResource tr = new TokenResource(); ServletContext context = EasyMock.createNiceMock(ServletContext.class); - //tr.context = context; - // tr.init(); HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); EasyMock.expect(request.getServletContext()).andReturn(context).anyTimes(); @@ -126,6 +123,7 @@ public class TokenServiceResourceTest { EasyMock.replay(principal, services, context, request, response); + TokenResource tr = new TokenResource(); tr.request = request; tr.response = response; @@ -142,7 +140,7 @@ public class TokenServiceResourceTest { assertNotNull(expiry); // Verify the token - JWTToken parsedToken = new JWTToken(accessToken); + JWT parsedToken = new JWTToken(accessToken); assertEquals("alice", parsedToken.getSubject()); assertTrue(authority.verifyToken(parsedToken)); } @@ -194,7 +192,7 @@ public class TokenServiceResourceTest { assertNotNull(expiry); // Verify the token - JWTToken parsedToken = new JWTToken(accessToken); + JWT parsedToken = new JWTToken(accessToken); assertEquals("alice", parsedToken.getSubject()); assertTrue(authority.verifyToken(parsedToken)); @@ -252,7 +250,7 @@ public class TokenServiceResourceTest { assertNotNull(expiry); // Verify the token - JWTToken parsedToken = new JWTToken(accessToken); + JWT parsedToken = new JWTToken(accessToken); assertEquals("alice", parsedToken.getSubject()); assertTrue(authority.verifyToken(parsedToken)); @@ -315,7 +313,7 @@ public class TokenServiceResourceTest { assertNotNull(expiry); // Verify the token - JWTToken parsedToken = new JWTToken(accessToken); + JWT parsedToken = new JWTToken(accessToken); assertEquals("alice", parsedToken.getSubject()); assertTrue(authority.verifyToken(parsedToken)); } @@ -405,6 +403,59 @@ public class TokenServiceResourceTest { assertEquals(403, retResponse.getStatus()); } + @Test + public void testSignatureAlgorithm() throws Exception { + ServletContext context = EasyMock.createNiceMock(ServletContext.class); + EasyMock.expect(context.getInitParameter("knox.token.audiences")).andReturn("recipient1,recipient2"); + EasyMock.expect(context.getInitParameter("knox.token.ttl")).andReturn(null); + EasyMock.expect(context.getInitParameter("knox.token.target.url")).andReturn(null); + EasyMock.expect(context.getInitParameter("knox.token.client.data")).andReturn(null); + EasyMock.expect(context.getInitParameter("knox.token.sigalg")).andReturn("RS512"); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + EasyMock.expect(request.getServletContext()).andReturn(context).anyTimes(); + Principal principal = EasyMock.createNiceMock(Principal.class); + EasyMock.expect(principal.getName()).andReturn("alice").anyTimes(); + EasyMock.expect(request.getUserPrincipal()).andReturn(principal).anyTimes(); + + GatewayServices services = EasyMock.createNiceMock(GatewayServices.class); + EasyMock.expect(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).andReturn(services); + + JWTokenAuthority authority = new TestJWTokenAuthority(publicKey, privateKey); + EasyMock.expect(services.getService(GatewayServices.TOKEN_SERVICE)).andReturn(authority); + + StringWriter writer = new StringWriter(); + PrintWriter printWriter = new PrintWriter(writer); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.getWriter()).andReturn(printWriter); + + EasyMock.replay(principal, services, context, request, response); + + TokenResource tr = new TokenResource(); + tr.request = request; + tr.response = response; + tr.context = context; + tr.init(); + + // Issue a token + Response retResponse = tr.doGet(); + + assertEquals(200, retResponse.getStatus()); + + // Parse the response + String retString = writer.toString(); + String accessToken = getTagValue(retString, "access_token"); + assertNotNull(accessToken); + String expiry = getTagValue(retString, "expires_in"); + assertNotNull(expiry); + + // Verify the token + JWT parsedToken = new JWTToken(accessToken); + assertEquals("alice", parsedToken.getSubject()); + assertTrue(authority.verifyToken(parsedToken)); + assertTrue(parsedToken.getHeader().contains("RS512")); + } + private String getTagValue(String token, String tagName) { String searchString = tagName + "\":"; String value = token.substring(token.indexOf(searchString) + searchString.length()); @@ -479,14 +530,9 @@ public class TokenServiceResourceTest { claimArray[3] = String.valueOf(expires); } - JWTToken token = null; - if ("RS256".equals(algorithm)) { - token = new JWTToken("RS256", claimArray, audiences); - JWSSigner signer = new RSASSASigner(privateKey); - token.sign(signer); - } else { - throw new TokenServiceException("Cannot issue token - Unsupported algorithm"); - } + JWT token = new JWTToken(algorithm, claimArray, audiences); + JWSSigner signer = new RSASSASigner(privateKey); + token.sign(signer); return token; } http://git-wip-us.apache.org/repos/asf/knox/blob/9c7aa7e1/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java ---------------------------------------------------------------------- diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java index 567c156..be2a331 100644 --- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java +++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java @@ -17,14 +17,11 @@ */ package org.apache.hadoop.gateway.services.security.token.impl; -import java.io.UnsupportedEncodingException; import java.text.ParseException; import java.util.Date; import java.util.ArrayList; import java.util.List; -import java.util.Map; -import org.apache.commons.codec.binary.Base64; import org.apache.hadoop.gateway.i18n.messages.MessagesFactory; import com.nimbusds.jose.JOSEException; http://git-wip-us.apache.org/repos/asf/knox/blob/9c7aa7e1/gateway-spi/src/test/java/org/apache/hadoop/gateway/services/security/token/impl/JWTTokenTest.java ---------------------------------------------------------------------- diff --git a/gateway-spi/src/test/java/org/apache/hadoop/gateway/services/security/token/impl/JWTTokenTest.java b/gateway-spi/src/test/java/org/apache/hadoop/gateway/services/security/token/impl/JWTTokenTest.java index 6372f0c..d971eca 100644 --- a/gateway-spi/src/test/java/org/apache/hadoop/gateway/services/security/token/impl/JWTTokenTest.java +++ b/gateway-spi/src/test/java/org/apache/hadoop/gateway/services/security/token/impl/JWTTokenTest.java @@ -22,9 +22,12 @@ import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; +import java.text.ParseException; import java.util.ArrayList; import java.util.Date; +import java.util.List; +import org.junit.BeforeClass; import org.junit.Test; import com.nimbusds.jose.JWSAlgorithm; @@ -37,10 +40,11 @@ public class JWTTokenTest extends org.junit.Assert { private static final String JWT_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MTY5MjkxMDksImp0aSI6ImFhN2Y4ZDBhOTVjIiwic2NvcGVzIjpbInJlcG8iLCJwdWJsaWNfcmVwbyJdfQ.XCEwpBGvOLma4TCoh36FU7XhUbcskygS81HE1uHLf0E"; private static final String HEADER = "{\"typ\":\"JWT\",\"alg\":\"HS256\"}"; - private RSAPublicKey publicKey; - private RSAPrivateKey privateKey; + private static RSAPublicKey publicKey; + private static RSAPrivateKey privateKey; - public JWTTokenTest() throws Exception, NoSuchAlgorithmException { + @BeforeClass + public static void setup() throws Exception, NoSuchAlgorithmException { KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(2048); @@ -64,7 +68,7 @@ public class JWTTokenTest extends org.junit.Assert { claims[1] = "john....@example.com"; claims[2] = "https://login.example.com"; claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); - JWTToken token = new JWTToken("RS256", claims); + JWT token = new JWTToken("RS256", claims); assertEquals("KNOXSSO", token.getIssuer()); assertEquals("john....@example.com", token.getSubject()); @@ -78,10 +82,10 @@ public class JWTTokenTest extends org.junit.Assert { claims[1] = "john....@example.com"; claims[2] = null; claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); - ArrayList<String> audiences = new ArrayList<String>(); + List<String> audiences = new ArrayList<String>(); audiences.add("https://login.example.com"); - JWTToken token = new JWTToken("RS256", claims, audiences); + JWT token = new JWTToken("RS256", claims, audiences); assertEquals("KNOXSSO", token.getIssuer()); assertEquals("john....@example.com", token.getSubject()); @@ -96,11 +100,11 @@ public class JWTTokenTest extends org.junit.Assert { claims[1] = "john....@example.com"; claims[2] = null; claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); - ArrayList<String> audiences = new ArrayList<String>(); + List<String> audiences = new ArrayList<String>(); audiences.add("https://login.example.com"); audiences.add("KNOXSSO"); - JWTToken token = new JWTToken("RS256", claims, audiences); + JWT token = new JWTToken("RS256", claims, audiences); assertEquals("KNOXSSO", token.getIssuer()); assertEquals("john....@example.com", token.getSubject()); @@ -134,9 +138,9 @@ public class JWTTokenTest extends org.junit.Assert { claims[1] = "john....@example.com"; claims[2] = null; claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); - ArrayList<String> audiences = null; + List<String> audiences = null; - JWTToken token = new JWTToken("RS256", claims, audiences); + JWT token = new JWTToken("RS256", claims, audiences); assertEquals("KNOXSSO", token.getIssuer()); assertEquals("john....@example.com", token.getSubject()); @@ -166,8 +170,7 @@ public class JWTTokenTest extends org.junit.Assert { claims[1] = "john....@example.com"; claims[2] = "https://login.example.com"; claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); - JWTToken token = new JWTToken("RS256", claims); - + JWT token = new JWTToken("RS256", claims); assertEquals("KNOXSSO", token.getIssuer()); assertEquals("john....@example.com", token.getSubject()); @@ -190,7 +193,7 @@ public class JWTTokenTest extends org.junit.Assert { claims[1] = "john....@example.com"; claims[2] = "https://login.example.com"; claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); - JWTToken token = new JWTToken(JWSAlgorithm.RS512.getName(), claims); + JWT token = new JWTToken(JWSAlgorithm.RS512.getName(), claims); assertEquals("KNOXSSO", token.getIssuer()); assertEquals("john....@example.com", token.getSubject()); @@ -214,10 +217,24 @@ public class JWTTokenTest extends org.junit.Assert { claims[1] = "john....@example.com"; claims[2] = "https://login.example.com"; claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300); - JWTToken token = new JWTToken("RS256", claims); + JWT token = new JWTToken("RS256", claims); assertNotNull(token.getExpires()); assertNotNull(token.getExpiresDate()); assertEquals(token.getExpiresDate(), new Date(Long.valueOf(token.getExpires()))); } + + @Test + public void testUnsignedToken() throws Exception { + String unsignedToken = "eyJhbGciOiJub25lIn0.eyJzdWIiOiJhbGljZSIsImp0aSI6ImY2YmNj" + + "MDVjLWI4MTktNGM0Mi1iMGMyLWJlYmY1MDE4YWFiZiJ9."; + + try { + new JWTToken(unsignedToken); + fail("Failure expected on an unsigned token"); + } catch (ParseException ex) { + // expected + } + } + }