/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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. */ package io.undertow.servlet.test.security.digest; import io.undertow.security.idm.DigestAlgorithm; import io.undertow.security.impl.DigestAuthorizationToken; import io.undertow.security.impl.DigestWWWAuthenticateToken; import io.undertow.server.handlers.PathHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.SecurityInfo.EmptyRoleSemantic; import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.WebResourceCollection; import io.undertow.servlet.test.SimpleServletTestCase; import io.undertow.servlet.test.security.SendAuthTypeServlet; import io.undertow.servlet.test.security.SendUsernameServlet; import io.undertow.servlet.test.security.constraint.ServletIdentityManager; import io.undertow.servlet.test.util.TestClassIntrospector; import io.undertow.testutils.DefaultServer; import io.undertow.testutils.HttpClientUtils; import io.undertow.testutils.TestHttpClient; import io.undertow.util.HexConverter; import io.undertow.util.StatusCodes; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Map; import javax.servlet.ServletException; import static io.undertow.util.Headers.AUTHORIZATION; import static io.undertow.util.Headers.DIGEST; import static io.undertow.util.Headers.WWW_AUTHENTICATE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Test case to test authentication using HTTP Digest. * * @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a> */ @RunWith(DefaultServer.class) public class DigestAuthTestCase { private static final String REALM_NAME = "Servlet_Realm"; private static final Charset UTF_8 = StandardCharsets.UTF_8; @BeforeClass public static void setup() throws ServletException { final PathHandler path = new PathHandler(); final ServletContainer container = ServletContainer.Factory.newInstance(); ServletInfo usernameServlet = new ServletInfo("Username Servlet", SendUsernameServlet.class) .addMapping("/secured/username"); ServletInfo authTypeServlet = new ServletInfo("Auth Type Servlet", SendAuthTypeServlet.class) .addMapping("/secured/authType"); ServletIdentityManager identityManager = new ServletIdentityManager(); identityManager.addUser("user1", "password1", "role1"); DeploymentInfo builder = new DeploymentInfo() .setClassLoader(SimpleServletTestCase.class.getClassLoader()) .setContextPath("/servletContext") .setClassIntrospecter(TestClassIntrospector.INSTANCE) .setDeploymentName("servletContext.war") .setIdentityManager(identityManager) .setLoginConfig(new LoginConfig("DIGEST", REALM_NAME)) .addServlets(usernameServlet, authTypeServlet); builder.addSecurityConstraint(new SecurityConstraint() .addWebResourceCollection(new WebResourceCollection() .addUrlPattern("/secured/*")) .addRoleAllowed("role1") .setEmptyRoleSemantic(EmptyRoleSemantic.DENY)); DeploymentManager manager = container.addDeployment(builder); manager.deploy(); path.addPrefixPath(builder.getContextPath(), manager.start()); DefaultServer.setRootHandler(path); } @Test public void testUserName() throws Exception { testCall("username", "user1"); } @Test public void testAuthType() throws Exception { testCall("authType", "DIGEST"); } public void testCall(final String path, final String expectedResponse) throws Exception { TestHttpClient client = new TestHttpClient(); String url = DefaultServer.getDefaultServerURL() + "/servletContext/secured/" + path; HttpGet get = new HttpGet(url); HttpResponse result = client.execute(get); assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); assertEquals(1, values.length); String value = values[0].getValue(); assertTrue(value.startsWith(DIGEST.toString())); Map<DigestWWWAuthenticateToken, String> parsedHeader = DigestWWWAuthenticateToken.parseHeader(value.substring(7)); assertEquals(REALM_NAME, parsedHeader.get(DigestWWWAuthenticateToken.REALM)); assertEquals(DigestAlgorithm.MD5.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.ALGORITHM)); assertTrue(parsedHeader.containsKey(DigestWWWAuthenticateToken.MESSAGE_QOP)); String nonce = parsedHeader.get(DigestWWWAuthenticateToken.NONCE); String clientResponse = createResponse("user1", REALM_NAME, "password1", "GET", "/", nonce); client = new TestHttpClient(); get = new HttpGet(url); StringBuilder sb = new StringBuilder(DIGEST.toString()); sb.append(" "); sb.append(DigestAuthorizationToken.USERNAME.getName()).append("=").append("\"user1\"").append(","); sb.append(DigestAuthorizationToken.REALM.getName()).append("=\"").append(REALM_NAME).append("\","); sb.append(DigestAuthorizationToken.NONCE.getName()).append("=\"").append(nonce).append("\","); sb.append(DigestAuthorizationToken.DIGEST_URI.getName()).append("=\"/\","); sb.append(DigestAuthorizationToken.RESPONSE.getName()).append("=\"").append(clientResponse).append("\""); get.addHeader(AUTHORIZATION.toString(), sb.toString()); result = client.execute(get); assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); final String response = HttpClientUtils.readResponse(result); assertEquals(expectedResponse, response); } /** * Creates a response value from the supplied parameters. * * @return The generated Hex encoded MD5 digest based response. */ private String createResponse(final String userName, final String realm, final String password, final String method, final String uri, final String nonce) throws Exception { MessageDigest digest = MessageDigest.getInstance("MD5"); digest.update(userName.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(realm.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(password.getBytes(UTF_8)); byte[] ha1 = HexConverter.convertToHexBytes(digest.digest()); digest.update(method.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(uri.getBytes(UTF_8)); byte[] ha2 = HexConverter.convertToHexBytes(digest.digest()); digest.update(ha1); digest.update((byte) ':'); digest.update(nonce.getBytes(UTF_8)); digest.update((byte) ':'); digest.update(ha2); return HexConverter.convertToHexString(digest.digest()); } }