HADOOP-11717. Support JWT tokens for web single sign on to the Hadoop
servers. (Larry McCay via omalley)
This commit is contained in:
parent
75c5454860
commit
ce63573314
|
@ -107,6 +107,17 @@
|
||||||
<artifactId>httpclient</artifactId>
|
<artifactId>httpclient</artifactId>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.nimbusds</groupId>
|
||||||
|
<artifactId>nimbus-jose-jwt</artifactId>
|
||||||
|
<scope>compile</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.directory.server</groupId>
|
<groupId>org.apache.directory.server</groupId>
|
||||||
<artifactId>apacheds-kerberos-codec</artifactId>
|
<artifactId>apacheds-kerberos-codec</artifactId>
|
||||||
|
|
|
@ -0,0 +1,363 @@
|
||||||
|
/**
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License. See accompanying LICENSE file.
|
||||||
|
*/
|
||||||
|
package org.apache.hadoop.security.authentication.server;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.text.ParseException;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
|
||||||
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
import org.apache.hadoop.security.authentication.client.AuthenticationException;
|
||||||
|
import org.apache.hadoop.security.authentication.server.AltKerberosAuthenticationHandler;
|
||||||
|
import org.apache.hadoop.security.authentication.server.AuthenticationToken;
|
||||||
|
import org.apache.hadoop.security.authentication.util.CertificateUtil;
|
||||||
|
import org.apache.hadoop.security.authentication.util.KerberosName;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.nimbusds.jwt.SignedJWT;
|
||||||
|
import com.nimbusds.jose.JOSEException;
|
||||||
|
import com.nimbusds.jose.JWSObject;
|
||||||
|
import com.nimbusds.jose.JWSVerifier;
|
||||||
|
import com.nimbusds.jose.crypto.RSASSAVerifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link JWTRedirectAuthenticationHandler} extends
|
||||||
|
* AltKerberosAuthenticationHandler to add WebSSO behavior for UIs. The expected
|
||||||
|
* SSO token is a JsonWebToken (JWT). The supported algorithm is RS256 which
|
||||||
|
* uses PKI between the token issuer and consumer. The flow requires a redirect
|
||||||
|
* to a configured authentication server URL and a subsequent request with the
|
||||||
|
* expected JWT token. This token is cryptographically verified and validated.
|
||||||
|
* The user identity is then extracted from the token and used to create an
|
||||||
|
* AuthenticationToken - as expected by the AuthenticationFilter.
|
||||||
|
*
|
||||||
|
* <p/>
|
||||||
|
* The supported configuration properties are:
|
||||||
|
* <ul>
|
||||||
|
* <li>authentication.provider.url: the full URL to the authentication server.
|
||||||
|
* This is the URL that the handler will redirect the browser to in order to
|
||||||
|
* authenticate the user. It does not have a default value.</li>
|
||||||
|
* <li>public.key.pem: This is the PEM formatted public key of the issuer of the
|
||||||
|
* JWT token. It is required for verifying that the issuer is a trusted party.
|
||||||
|
* DO NOT include the PEM header and footer portions of the PEM encoded
|
||||||
|
* certificate. It does not have a default value.</li>
|
||||||
|
* <li>expected.jwt.audiences: This is a list of strings that identify
|
||||||
|
* acceptable audiences for the JWT token. The audience is a way for the issuer
|
||||||
|
* to indicate what entity/s that the token is intended for. Default value is
|
||||||
|
* null which indicates that all audiences will be accepted.</li>
|
||||||
|
* <li>jwt.cookie.name: the name of the cookie that contains the JWT token.
|
||||||
|
* Default value is "hadoop-jwt".</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public class JWTRedirectAuthenticationHandler extends
|
||||||
|
AltKerberosAuthenticationHandler {
|
||||||
|
private static Logger LOG = LoggerFactory
|
||||||
|
.getLogger(JWTRedirectAuthenticationHandler.class);
|
||||||
|
|
||||||
|
public static final String AUTHENTICATION_PROVIDER_URL = "authentication.provider.url";
|
||||||
|
public static final String PUBLIC_KEY_PEM = "public.key.pem";
|
||||||
|
public static final String EXPECTED_JWT_AUDIENCES = "expected.jwt.audiences";
|
||||||
|
public static final String JWT_COOKIE_NAME = "jwt.cookie.name";
|
||||||
|
private static final String ORIGINAL_URL_QUERY_PARAM = "originalUrl=";
|
||||||
|
private String authenticationProviderUrl = null;
|
||||||
|
private RSAPublicKey publicKey = null;
|
||||||
|
private List<String> audiences = null;
|
||||||
|
private String cookieName = "hadoop-jwt";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primarily for testing, this provides a way to set the publicKey for
|
||||||
|
* signature verification without needing to get a PEM encoded value.
|
||||||
|
*
|
||||||
|
* @param pk
|
||||||
|
*/
|
||||||
|
public void setPublicKey(RSAPublicKey pk) {
|
||||||
|
publicKey = pk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the authentication handler instance.
|
||||||
|
* <p/>
|
||||||
|
* This method is invoked by the {@link AuthenticationFilter#init} method.
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* configuration properties to initialize the handler.
|
||||||
|
*
|
||||||
|
* @throws ServletException
|
||||||
|
* thrown if the handler could not be initialized.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void init(Properties config) throws ServletException {
|
||||||
|
super.init(config);
|
||||||
|
// setup the URL to redirect to for authentication
|
||||||
|
authenticationProviderUrl = config
|
||||||
|
.getProperty(AUTHENTICATION_PROVIDER_URL);
|
||||||
|
if (authenticationProviderUrl == null) {
|
||||||
|
throw new ServletException(
|
||||||
|
"Authentication provider URL must not be null - configure: "
|
||||||
|
+ AUTHENTICATION_PROVIDER_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup the public key of the token issuer for verification
|
||||||
|
if (publicKey == null) {
|
||||||
|
String pemPublicKey = config.getProperty(PUBLIC_KEY_PEM);
|
||||||
|
if (pemPublicKey == null) {
|
||||||
|
throw new ServletException(
|
||||||
|
"Public key for signature validation must be provisioned.");
|
||||||
|
}
|
||||||
|
publicKey = CertificateUtil.parseRSAPublicKey(pemPublicKey);
|
||||||
|
}
|
||||||
|
// setup the list of valid audiences for token validation
|
||||||
|
String auds = config.getProperty(EXPECTED_JWT_AUDIENCES);
|
||||||
|
if (auds != null) {
|
||||||
|
// parse into the list
|
||||||
|
String[] audArray = auds.split(",");
|
||||||
|
audiences = new ArrayList<String>();
|
||||||
|
for (String a : audArray) {
|
||||||
|
audiences.add(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup custom cookie name if configured
|
||||||
|
String customCookieName = config.getProperty(JWT_COOKIE_NAME);
|
||||||
|
if (customCookieName != null) {
|
||||||
|
cookieName = customCookieName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthenticationToken alternateAuthenticate(HttpServletRequest request,
|
||||||
|
HttpServletResponse response) throws IOException,
|
||||||
|
AuthenticationException {
|
||||||
|
AuthenticationToken token = null;
|
||||||
|
|
||||||
|
String serializedJWT = null;
|
||||||
|
HttpServletRequest req = (HttpServletRequest) request;
|
||||||
|
serializedJWT = getJWTFromCookie(req);
|
||||||
|
if (serializedJWT == null) {
|
||||||
|
String loginURL = constructLoginURL(request, response);
|
||||||
|
LOG.info("sending redirect to: " + loginURL);
|
||||||
|
((HttpServletResponse) response).sendRedirect(loginURL);
|
||||||
|
} else {
|
||||||
|
String userName = null;
|
||||||
|
SignedJWT jwtToken = null;
|
||||||
|
boolean valid = false;
|
||||||
|
try {
|
||||||
|
jwtToken = SignedJWT.parse(serializedJWT);
|
||||||
|
valid = validateToken(jwtToken);
|
||||||
|
if (valid) {
|
||||||
|
userName = jwtToken.getJWTClaimsSet().getSubject();
|
||||||
|
LOG.info("USERNAME: " + userName);
|
||||||
|
} else {
|
||||||
|
LOG.warn("jwtToken failed validation: " + jwtToken.serialize());
|
||||||
|
}
|
||||||
|
} catch(ParseException pe) {
|
||||||
|
// unable to parse the token let's try and get another one
|
||||||
|
LOG.warn("Unable to parse the JWT token", pe);
|
||||||
|
}
|
||||||
|
if (valid) {
|
||||||
|
LOG.debug("Issuing AuthenticationToken for user.");
|
||||||
|
token = new AuthenticationToken(userName, userName, getType());
|
||||||
|
} else {
|
||||||
|
String loginURL = constructLoginURL(request, response);
|
||||||
|
LOG.info("token validation failed - sending redirect to: " + loginURL);
|
||||||
|
((HttpServletResponse) response).sendRedirect(loginURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulate the acquisition of the JWT token from HTTP cookies within the
|
||||||
|
* request.
|
||||||
|
*
|
||||||
|
* @param serializedJWT
|
||||||
|
* @param req
|
||||||
|
* @return serialized JWT token
|
||||||
|
*/
|
||||||
|
protected String getJWTFromCookie(HttpServletRequest req) {
|
||||||
|
String serializedJWT = null;
|
||||||
|
Cookie[] cookies = req.getCookies();
|
||||||
|
String userName = null;
|
||||||
|
if (cookies != null) {
|
||||||
|
for (Cookie cookie : cookies) {
|
||||||
|
if (cookieName.equals(cookie.getName())) {
|
||||||
|
LOG.info(cookieName
|
||||||
|
+ " cookie has been found and is being processed");
|
||||||
|
serializedJWT = cookie.getValue();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return serializedJWT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the URL to be used for authentication of the user in the absence of
|
||||||
|
* a JWT token within the incoming request.
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* @param response
|
||||||
|
* @return url to use as login url for redirect
|
||||||
|
*/
|
||||||
|
protected String constructLoginURL(HttpServletRequest request,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
String delimiter = "?";
|
||||||
|
if (authenticationProviderUrl.contains("?")) {
|
||||||
|
delimiter = "&";
|
||||||
|
}
|
||||||
|
String loginURL = authenticationProviderUrl + delimiter
|
||||||
|
+ ORIGINAL_URL_QUERY_PARAM
|
||||||
|
+ request.getRequestURL().toString();
|
||||||
|
return loginURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method provides a single method for validating the JWT for use in
|
||||||
|
* request processing. It provides for the override of specific aspects of
|
||||||
|
* this implementation through submethods used within but also allows for the
|
||||||
|
* override of the entire token validation algorithm.
|
||||||
|
*
|
||||||
|
* @param jwtToken
|
||||||
|
* @return true if valid
|
||||||
|
* @throws AuthenticationException
|
||||||
|
*/
|
||||||
|
protected boolean validateToken(SignedJWT jwtToken) {
|
||||||
|
boolean sigValid = validateSignature(jwtToken);
|
||||||
|
if (!sigValid) {
|
||||||
|
LOG.warn("Signature could not be verified");
|
||||||
|
}
|
||||||
|
boolean audValid = validateAudiences(jwtToken);
|
||||||
|
if (!audValid) {
|
||||||
|
LOG.warn("Audience validation failed.");
|
||||||
|
}
|
||||||
|
boolean expValid = validateExpiration(jwtToken);
|
||||||
|
if (!expValid) {
|
||||||
|
LOG.info("Expiration validation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sigValid && audValid && expValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the signature of the JWT token in this method. This method depends
|
||||||
|
* on the public key that was established during init based upon the
|
||||||
|
* provisioned public key. Override this method in subclasses in order to
|
||||||
|
* customize the signature verification behavior.
|
||||||
|
*
|
||||||
|
* @param jwtToken
|
||||||
|
* @throws AuthenticationException
|
||||||
|
*/
|
||||||
|
protected boolean validateSignature(SignedJWT jwtToken) {
|
||||||
|
boolean valid = false;
|
||||||
|
if (JWSObject.State.SIGNED == jwtToken.getState()) {
|
||||||
|
LOG.debug("JWT token is in a SIGNED state");
|
||||||
|
if (jwtToken.getSignature() != null) {
|
||||||
|
LOG.debug("JWT token signature is not null");
|
||||||
|
try {
|
||||||
|
JWSVerifier verifier = new RSASSAVerifier(publicKey);
|
||||||
|
if (jwtToken.verify(verifier)) {
|
||||||
|
valid = true;
|
||||||
|
LOG.debug("JWT token has been successfully verified");
|
||||||
|
} else {
|
||||||
|
LOG.warn("JWT signature verification failed.");
|
||||||
|
}
|
||||||
|
} catch (JOSEException je) {
|
||||||
|
LOG.warn("Error while validating signature", je);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate whether any of the accepted audience claims is present in the
|
||||||
|
* issued token claims list for audience. Override this method in subclasses
|
||||||
|
* in order to customize the audience validation behavior.
|
||||||
|
*
|
||||||
|
* @param jwtToken
|
||||||
|
* the JWT token where the allowed audiences will be found
|
||||||
|
* @return true if an expected audience is present, otherwise false
|
||||||
|
*/
|
||||||
|
protected boolean validateAudiences(SignedJWT jwtToken) {
|
||||||
|
boolean valid = false;
|
||||||
|
try {
|
||||||
|
List<String> tokenAudienceList = jwtToken.getJWTClaimsSet()
|
||||||
|
.getAudience();
|
||||||
|
// if there were no expected audiences configured then just
|
||||||
|
// consider any audience acceptable
|
||||||
|
if (audiences == null) {
|
||||||
|
valid = true;
|
||||||
|
} else {
|
||||||
|
// if any of the configured audiences is found then consider it
|
||||||
|
// acceptable
|
||||||
|
boolean found = false;
|
||||||
|
for (String aud : tokenAudienceList) {
|
||||||
|
if (audiences.contains(aud)) {
|
||||||
|
LOG.debug("JWT token audience has been successfully validated");
|
||||||
|
valid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!valid) {
|
||||||
|
LOG.warn("JWT audience validation failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ParseException pe) {
|
||||||
|
LOG.warn("Unable to parse the JWT token.", pe);
|
||||||
|
}
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that the expiration time of the JWT token has not been violated.
|
||||||
|
* If it has then throw an AuthenticationException. Override this method in
|
||||||
|
* subclasses in order to customize the expiration validation behavior.
|
||||||
|
*
|
||||||
|
* @param jwtToken
|
||||||
|
* @throws AuthenticationException
|
||||||
|
*/
|
||||||
|
protected boolean validateExpiration(SignedJWT jwtToken) {
|
||||||
|
boolean valid = false;
|
||||||
|
try {
|
||||||
|
Date expires = jwtToken.getJWTClaimsSet().getExpirationTime();
|
||||||
|
if (expires != null && new Date().before(expires)) {
|
||||||
|
LOG.debug("JWT token expiration date has been "
|
||||||
|
+ "successfully validated");
|
||||||
|
valid = true;
|
||||||
|
} else {
|
||||||
|
LOG.warn("JWT expiration date validation failed.");
|
||||||
|
}
|
||||||
|
} catch (ParseException pe) {
|
||||||
|
LOG.warn("JWT expiration date validation failed.", pe);
|
||||||
|
}
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.hadoop.security.authentication.util;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
|
||||||
|
public class CertificateUtil {
|
||||||
|
private static final String PEM_HEADER = "-----BEGIN CERTIFICATE-----\n";
|
||||||
|
private static final String PEM_FOOTER = "\n-----END CERTIFICATE-----";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an RSAPublicKey from the provided PEM encoding.
|
||||||
|
*
|
||||||
|
* @param pem
|
||||||
|
* - the pem encoding from config without the header and footer
|
||||||
|
* @return RSAPublicKey
|
||||||
|
*/
|
||||||
|
public static RSAPublicKey parseRSAPublicKey(String pem) throws ServletException {
|
||||||
|
String fullPem = PEM_HEADER + pem + PEM_FOOTER;
|
||||||
|
PublicKey key = null;
|
||||||
|
try {
|
||||||
|
CertificateFactory fact = CertificateFactory.getInstance("X.509");
|
||||||
|
ByteArrayInputStream is = new ByteArrayInputStream(
|
||||||
|
fullPem.getBytes("UTF8"));
|
||||||
|
|
||||||
|
X509Certificate cer = (X509Certificate) fact.generateCertificate(is);
|
||||||
|
key = cer.getPublicKey();
|
||||||
|
} catch (CertificateException ce) {
|
||||||
|
String message = null;
|
||||||
|
if (pem.startsWith(PEM_HEADER)) {
|
||||||
|
message = "CertificateException - be sure not to include PEM header "
|
||||||
|
+ "and footer in the PEM configuration element.";
|
||||||
|
} else {
|
||||||
|
message = "CertificateException - PEM may be corrupt";
|
||||||
|
}
|
||||||
|
throw new ServletException(message, ce);
|
||||||
|
} catch (UnsupportedEncodingException uee) {
|
||||||
|
throw new ServletException(uee);
|
||||||
|
}
|
||||||
|
return (RSAPublicKey) key;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,418 @@
|
||||||
|
/**
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License. See accompanying LICENSE file.
|
||||||
|
*/
|
||||||
|
package org.apache.hadoop.security.authentication.server;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.interfaces.RSAPrivateKey;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.Vector;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.hadoop.minikdc.KerberosSecurityTestcase;
|
||||||
|
import org.apache.hadoop.security.authentication.KerberosTestUtils;
|
||||||
|
import org.apache.hadoop.security.authentication.client.AuthenticationException;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
import com.nimbusds.jose.*;
|
||||||
|
import com.nimbusds.jwt.JWTClaimsSet;
|
||||||
|
import com.nimbusds.jwt.SignedJWT;
|
||||||
|
import com.nimbusds.jose.crypto.RSASSASigner;
|
||||||
|
import com.nimbusds.jose.crypto.RSASSAVerifier;
|
||||||
|
import com.nimbusds.jose.util.Base64URL;
|
||||||
|
|
||||||
|
public class TestJWTRedirectAuthentictionHandler extends
|
||||||
|
KerberosSecurityTestcase {
|
||||||
|
private static final String SERVICE_URL = "https://localhost:8888/resource";
|
||||||
|
private static final String REDIRECT_LOCATION =
|
||||||
|
"https://localhost:8443/authserver?originalUrl=" + SERVICE_URL;
|
||||||
|
RSAPublicKey publicKey = null;
|
||||||
|
RSAPrivateKey privateKey = null;
|
||||||
|
JWTRedirectAuthenticationHandler handler = null;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoPublicKeyJWT() throws Exception {
|
||||||
|
try {
|
||||||
|
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));
|
||||||
|
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
|
||||||
|
Mockito.when(response.encodeRedirectURL(SERVICE_URL)).thenReturn(
|
||||||
|
SERVICE_URL);
|
||||||
|
|
||||||
|
AuthenticationToken token = handler.alternateAuthenticate(request,
|
||||||
|
response);
|
||||||
|
fail("alternateAuthentication should have thrown a ServletException");
|
||||||
|
} catch (ServletException se) {
|
||||||
|
assertTrue(se.getMessage().contains(
|
||||||
|
"Public key for signature validation must be provisioned"));
|
||||||
|
} catch (AuthenticationException ae) {
|
||||||
|
fail("alternateAuthentication should NOT have thrown a AuthenticationException");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCustomCookieNameJWT() throws Exception {
|
||||||
|
try {
|
||||||
|
handler.setPublicKey(publicKey);
|
||||||
|
|
||||||
|
Properties props = getProperties();
|
||||||
|
props.put(JWTRedirectAuthenticationHandler.JWT_COOKIE_NAME, "jowt");
|
||||||
|
handler.init(props);
|
||||||
|
|
||||||
|
SignedJWT jwt = getJWT("bob", new Date(new Date().getTime() + 5000),
|
||||||
|
privateKey);
|
||||||
|
|
||||||
|
Cookie cookie = new Cookie("jowt", 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));
|
||||||
|
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
|
||||||
|
Mockito.when(response.encodeRedirectURL(SERVICE_URL)).thenReturn(
|
||||||
|
SERVICE_URL);
|
||||||
|
|
||||||
|
AuthenticationToken token = handler.alternateAuthenticate(request,
|
||||||
|
response);
|
||||||
|
Assert.assertEquals("bob", token.getUserName());
|
||||||
|
} catch (ServletException se) {
|
||||||
|
fail("alternateAuthentication should NOT have thrown a ServletException: "
|
||||||
|
+ se.getMessage());
|
||||||
|
} catch (AuthenticationException ae) {
|
||||||
|
fail("alternateAuthentication should NOT have thrown a AuthenticationException");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoProviderURLJWT() throws Exception {
|
||||||
|
try {
|
||||||
|
handler.setPublicKey(publicKey);
|
||||||
|
|
||||||
|
Properties props = getProperties();
|
||||||
|
props
|
||||||
|
.remove(JWTRedirectAuthenticationHandler.AUTHENTICATION_PROVIDER_URL);
|
||||||
|
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));
|
||||||
|
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
|
||||||
|
Mockito.when(response.encodeRedirectURL(SERVICE_URL)).thenReturn(
|
||||||
|
SERVICE_URL);
|
||||||
|
|
||||||
|
AuthenticationToken token = handler.alternateAuthenticate(request,
|
||||||
|
response);
|
||||||
|
fail("alternateAuthentication should have thrown an AuthenticationException");
|
||||||
|
} catch (ServletException se) {
|
||||||
|
assertTrue(se.getMessage().contains(
|
||||||
|
"Authentication provider URL must not be null"));
|
||||||
|
} catch (AuthenticationException ae) {
|
||||||
|
fail("alternateAuthentication should NOT have thrown a AuthenticationException");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnableToParseJWT() throws Exception {
|
||||||
|
try {
|
||||||
|
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", "ljm" + 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));
|
||||||
|
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 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));
|
||||||
|
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 testExpiredJWT() throws Exception {
|
||||||
|
try {
|
||||||
|
handler.setPublicKey(publicKey);
|
||||||
|
|
||||||
|
Properties props = getProperties();
|
||||||
|
handler.init(props);
|
||||||
|
|
||||||
|
SignedJWT jwt = getJWT("bob", new Date(new Date().getTime() - 1000),
|
||||||
|
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));
|
||||||
|
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 testInvalidAudienceJWT() throws Exception {
|
||||||
|
try {
|
||||||
|
handler.setPublicKey(publicKey);
|
||||||
|
|
||||||
|
Properties props = getProperties();
|
||||||
|
props
|
||||||
|
.put(JWTRedirectAuthenticationHandler.EXPECTED_JWT_AUDIENCES, "foo");
|
||||||
|
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));
|
||||||
|
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 testValidAudienceJWT() throws Exception {
|
||||||
|
try {
|
||||||
|
handler.setPublicKey(publicKey);
|
||||||
|
|
||||||
|
Properties props = getProperties();
|
||||||
|
props
|
||||||
|
.put(JWTRedirectAuthenticationHandler.EXPECTED_JWT_AUDIENCES, "bar");
|
||||||
|
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));
|
||||||
|
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
|
||||||
|
Mockito.when(response.encodeRedirectURL(SERVICE_URL)).thenReturn(
|
||||||
|
SERVICE_URL);
|
||||||
|
|
||||||
|
AuthenticationToken token = handler.alternateAuthenticate(request,
|
||||||
|
response);
|
||||||
|
Assert.assertEquals("bob", token.getUserName());
|
||||||
|
} catch (ServletException se) {
|
||||||
|
fail("alternateAuthentication should NOT have thrown a ServletException");
|
||||||
|
} catch (AuthenticationException ae) {
|
||||||
|
fail("alternateAuthentication should NOT have thrown an AuthenticationException");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidJWT() throws Exception {
|
||||||
|
try {
|
||||||
|
handler.setPublicKey(publicKey);
|
||||||
|
|
||||||
|
Properties props = getProperties();
|
||||||
|
handler.init(props);
|
||||||
|
|
||||||
|
SignedJWT jwt = getJWT("alice", 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));
|
||||||
|
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
|
||||||
|
Mockito.when(response.encodeRedirectURL(SERVICE_URL)).thenReturn(
|
||||||
|
SERVICE_URL);
|
||||||
|
|
||||||
|
AuthenticationToken token = handler.alternateAuthenticate(request,
|
||||||
|
response);
|
||||||
|
Assert.assertNotNull("Token should not be null.", token);
|
||||||
|
Assert.assertEquals("alice", token.getUserName());
|
||||||
|
} catch (ServletException se) {
|
||||||
|
fail("alternateAuthentication should NOT have thrown a ServletException.");
|
||||||
|
} catch (AuthenticationException ae) {
|
||||||
|
fail("alternateAuthentication should NOT have thrown an AuthenticationException");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() throws Exception, NoSuchAlgorithmException {
|
||||||
|
setupKerberosRequirements();
|
||||||
|
|
||||||
|
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
|
||||||
|
kpg.initialize(2048);
|
||||||
|
|
||||||
|
KeyPair kp = kpg.genKeyPair();
|
||||||
|
publicKey = (RSAPublicKey) kp.getPublic();
|
||||||
|
privateKey = (RSAPrivateKey) kp.getPrivate();
|
||||||
|
|
||||||
|
handler = new JWTRedirectAuthenticationHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setupKerberosRequirements() throws Exception {
|
||||||
|
String[] keytabUsers = new String[] { "HTTP/host1", "HTTP/host2",
|
||||||
|
"HTTP2/host1", "XHTTP/host" };
|
||||||
|
String keytab = KerberosTestUtils.getKeytabFile();
|
||||||
|
getKdc().createPrincipal(new File(keytab), keytabUsers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void teardown() throws Exception {
|
||||||
|
handler.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Properties getProperties() {
|
||||||
|
Properties props = new Properties();
|
||||||
|
props.setProperty(
|
||||||
|
JWTRedirectAuthenticationHandler.AUTHENTICATION_PROVIDER_URL,
|
||||||
|
"https://localhost:8443/authserver");
|
||||||
|
props.setProperty("kerberos.principal",
|
||||||
|
KerberosTestUtils.getServerPrincipal());
|
||||||
|
props.setProperty("kerberos.keytab", KerberosTestUtils.getKeytabFile());
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SignedJWT getJWT(String sub, Date expires, RSAPrivateKey privateKey)
|
||||||
|
throws Exception {
|
||||||
|
JWTClaimsSet claimsSet = new JWTClaimsSet();
|
||||||
|
claimsSet.setSubject(sub);
|
||||||
|
claimsSet.setIssueTime(new Date(new Date().getTime()));
|
||||||
|
claimsSet.setIssuer("https://c2id.com");
|
||||||
|
claimsSet.setCustomClaim("scope", "openid");
|
||||||
|
claimsSet.setExpirationTime(expires);
|
||||||
|
List<String> aud = new ArrayList<String>();
|
||||||
|
aud.add("bar");
|
||||||
|
claimsSet.setAudience("bar");
|
||||||
|
|
||||||
|
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).build();
|
||||||
|
|
||||||
|
SignedJWT signedJWT = new SignedJWT(header, claimsSet);
|
||||||
|
Base64URL sigInput = Base64URL.encode(signedJWT.getSigningInput());
|
||||||
|
JWSSigner signer = new RSASSASigner(privateKey);
|
||||||
|
|
||||||
|
signedJWT.sign(signer);
|
||||||
|
|
||||||
|
return signedJWT;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.hadoop.security.authentication.util;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class TestCertificateUtil {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidPEMWithHeaderAndFooter() throws Exception {
|
||||||
|
String pem = "-----BEGIN CERTIFICATE-----\n"
|
||||||
|
+ "MIICOjCCAaOgAwIBAgIJANXi/oWxvJNzMA0GCSqGSIb3DQEBBQUAMF8xCzAJBgNVBAYTAlVTMQ0w"
|
||||||
|
+ "CwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ8wDQYDVQQKEwZIYWRvb3AxDTALBgNVBAsTBFRl"
|
||||||
|
+ "c3QxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNTAxMDIyMTE5MjRaFw0xNjAxMDIyMTE5MjRaMF8x"
|
||||||
|
+ "CzAJBgNVBAYTAlVTMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ8wDQYDVQQKEwZIYWRv"
|
||||||
|
+ "b3AxDTALBgNVBAsTBFRlc3QxEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOB"
|
||||||
|
+ "jQAwgYkCgYEAwpfpLdi7dWTHNzETt+L7618/dWUQFb/C7o1jIxFgbKOVIB6d5YmvUbJck5PYxFkz"
|
||||||
|
+ "C25fmU5H71WGOI1Kle5TFDmIo+hqh5xqu1YNRZz9i6D94g+2AyYr9BpvH4ZfdHs7r9AU7c3kq68V"
|
||||||
|
+ "7OPuuaHb25J8isiOyA3RiWuJGQlXTdkCAwEAATANBgkqhkiG9w0BAQUFAAOBgQAdRUyCUqE9sdim"
|
||||||
|
+ "Fbll9BuZDKV16WXeWGq+kTd7ETe7l0fqXjq5EnrifOai0L/pXwVvS2jrFkKQRlRxRGUNaeEBZ2Wy"
|
||||||
|
+ "9aTyR+HGHCfvwoCegc9rAVw/DLaRriSO/jnEXzYK6XLVKH+hx5UXrJ7Oyc7JjZUc3g9kCWORThCX"
|
||||||
|
+ "Mzc1xA==" + "\n-----END CERTIFICATE-----";
|
||||||
|
try {
|
||||||
|
CertificateUtil.parseRSAPublicKey(pem);
|
||||||
|
fail("Should not have thrown ServletException");
|
||||||
|
} catch (ServletException se) {
|
||||||
|
assertTrue(se.getMessage().contains("PEM header"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCorruptPEM() throws Exception {
|
||||||
|
String pem = "LJMLJMMIICOjCCAaOgAwIBAgIJANXi/oWxvJNzMA0GCSqGSIb3DQEBBQUAMF8xCzAJBgNVBAYTAlVTMQ0w"
|
||||||
|
+ "CwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ8wDQYDVQQKEwZIYWRvb3AxDTALBgNVBAsTBFRl"
|
||||||
|
+ "c3QxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNTAxMDIyMTE5MjRaFw0xNjAxMDIyMTE5MjRaMF8x"
|
||||||
|
+ "CzAJBgNVBAYTAlVTMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ8wDQYDVQQKEwZIYWRv"
|
||||||
|
+ "b3AxDTALBgNVBAsTBFRlc3QxEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOB"
|
||||||
|
+ "jQAwgYkCgYEAwpfpLdi7dWTHNzETt+L7618/dWUQFb/C7o1jIxFgbKOVIB6d5YmvUbJck5PYxFkz"
|
||||||
|
+ "C25fmU5H71WGOI1Kle5TFDmIo+hqh5xqu1YNRZz9i6D94g+2AyYr9BpvH4ZfdHs7r9AU7c3kq68V"
|
||||||
|
+ "7OPuuaHb25J8isiOyA3RiWuJGQlXTdkCAwEAATANBgkqhkiG9w0BAQUFAAOBgQAdRUyCUqE9sdim"
|
||||||
|
+ "Fbll9BuZDKV16WXeWGq+kTd7ETe7l0fqXjq5EnrifOai0L/pXwVvS2jrFkKQRlRxRGUNaeEBZ2Wy"
|
||||||
|
+ "9aTyR+HGHCfvwoCegc9rAVw/DLaRriSO/jnEXzYK6XLVKH+hx5UXrJ7Oyc7JjZUc3g9kCWORThCX"
|
||||||
|
+ "Mzc1xA==";
|
||||||
|
try {
|
||||||
|
CertificateUtil.parseRSAPublicKey(pem);
|
||||||
|
fail("Should not have thrown ServletException");
|
||||||
|
} catch (ServletException se) {
|
||||||
|
assertTrue(se.getMessage().contains("corrupt"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidPEM() throws Exception {
|
||||||
|
String pem = "MIICOjCCAaOgAwIBAgIJANXi/oWxvJNzMA0GCSqGSIb3DQEBBQUAMF8xCzAJBgNVBAYTAlVTMQ0w"
|
||||||
|
+ "CwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ8wDQYDVQQKEwZIYWRvb3AxDTALBgNVBAsTBFRl"
|
||||||
|
+ "c3QxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNTAxMDIyMTE5MjRaFw0xNjAxMDIyMTE5MjRaMF8x"
|
||||||
|
+ "CzAJBgNVBAYTAlVTMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ8wDQYDVQQKEwZIYWRv"
|
||||||
|
+ "b3AxDTALBgNVBAsTBFRlc3QxEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOB"
|
||||||
|
+ "jQAwgYkCgYEAwpfpLdi7dWTHNzETt+L7618/dWUQFb/C7o1jIxFgbKOVIB6d5YmvUbJck5PYxFkz"
|
||||||
|
+ "C25fmU5H71WGOI1Kle5TFDmIo+hqh5xqu1YNRZz9i6D94g+2AyYr9BpvH4ZfdHs7r9AU7c3kq68V"
|
||||||
|
+ "7OPuuaHb25J8isiOyA3RiWuJGQlXTdkCAwEAATANBgkqhkiG9w0BAQUFAAOBgQAdRUyCUqE9sdim"
|
||||||
|
+ "Fbll9BuZDKV16WXeWGq+kTd7ETe7l0fqXjq5EnrifOai0L/pXwVvS2jrFkKQRlRxRGUNaeEBZ2Wy"
|
||||||
|
+ "9aTyR+HGHCfvwoCegc9rAVw/DLaRriSO/jnEXzYK6XLVKH+hx5UXrJ7Oyc7JjZUc3g9kCWORThCX"
|
||||||
|
+ "Mzc1xA==";
|
||||||
|
try {
|
||||||
|
RSAPublicKey pk = CertificateUtil.parseRSAPublicKey(pem);
|
||||||
|
assertTrue(pk != null);
|
||||||
|
assertTrue(pk.getAlgorithm().equals("RSA"));
|
||||||
|
} catch (ServletException se) {
|
||||||
|
fail("Should not have thrown ServletException");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -481,6 +481,9 @@ Release 2.8.0 - UNRELEASED
|
||||||
HADOOP-9805. Refactor RawLocalFileSystem#rename for improved testability.
|
HADOOP-9805. Refactor RawLocalFileSystem#rename for improved testability.
|
||||||
(Jean-Pierre Matsumoto via cnauroth)
|
(Jean-Pierre Matsumoto via cnauroth)
|
||||||
|
|
||||||
|
HADOOP-11717. Support JWT tokens for web single sign on to the Hadoop
|
||||||
|
servers. (Larry McCay via omalley)
|
||||||
|
|
||||||
OPTIMIZATIONS
|
OPTIMIZATIONS
|
||||||
|
|
||||||
HADOOP-11785. Reduce the number of listStatus operation in distcp
|
HADOOP-11785. Reduce the number of listStatus operation in distcp
|
||||||
|
|
|
@ -951,6 +951,19 @@
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.nimbusds</groupId>
|
||||||
|
<artifactId>nimbus-jose-jwt</artifactId>
|
||||||
|
<version>3.9</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue