Author: markt
Date: Fri Jun 26 08:07:37 2015
New Revision: 1687710

URL: http://svn.apache.org/r1687710
Log:
Ported test to validate JASPIC DIGEST implementation 
Patch by fjodorver

Added:
    
tomcat/trunk/test/org/apache/catalina/authenticator/TestJaspicDigestAuthenticator.java
   (with props)
Modified:
    
tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/DigestAuthModule.java

Modified: 
tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/DigestAuthModule.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/DigestAuthModule.java?rev=1687710&r1=1687709&r2=1687710&view=diff
==============================================================================
--- 
tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/DigestAuthModule.java
 (original)
+++ 
tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/DigestAuthModule.java
 Fri Jun 26 08:07:37 2015
@@ -238,18 +238,13 @@ public class DigestAuthModule extends To
 
         DigestInfo digestInfo = new DigestInfo(getOpaque(), 
getNonceValidity(), getKey(), nonces,
                 isValidateUri(), getRealmName());
-        if (authorization == null) {
 
+        if (authorization == null || !digestInfo.parse(request, 
authorization)) {
             String nonce = generateNonce(request);
-
             String authenticateHeader = getAuthenticateHeader(nonce, false);
             return sendUnauthorizedError(response, authenticateHeader);
         }
 
-        if (!digestInfo.parse(request, authorization)) {
-            return AuthStatus.SEND_FAILURE;
-        }
-
         if (digestInfo.validate(request)) {
             principal = digestInfo.authenticate(realm);
         }

Added: 
tomcat/trunk/test/org/apache/catalina/authenticator/TestJaspicDigestAuthenticator.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/authenticator/TestJaspicDigestAuthenticator.java?rev=1687710&view=auto
==============================================================================
--- 
tomcat/trunk/test/org/apache/catalina/authenticator/TestJaspicDigestAuthenticator.java
 (added)
+++ 
tomcat/trunk/test/org/apache/catalina/authenticator/TestJaspicDigestAuthenticator.java
 Fri Jun 26 08:07:37 2015
@@ -0,0 +1,410 @@
+/*
+ *  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.catalina.authenticator;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.security.auth.message.config.AuthConfigFactory;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.authenticator.jaspic.JaspicAuthenticator;
+import 
org.apache.catalina.authenticator.jaspic.provider.TomcatAuthConfigProvider;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.core.TesterContext;
+import org.apache.catalina.startup.TesterMapRealm;
+import org.apache.catalina.startup.TesterServlet;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.descriptor.web.LoginConfig;
+import org.apache.tomcat.util.descriptor.web.SecurityCollection;
+import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
+import org.apache.tomcat.util.security.ConcurrentMessageDigest;
+import org.apache.tomcat.util.security.MD5Encoder;
+
+public class TestJaspicDigestAuthenticator extends TomcatBaseTest {
+
+    private static String USER = "user";
+    private static String PWD = "pwd";
+    private static String ROLE = "role";
+    private static String URI = "/protected";
+    private static String QUERY = "?foo=bar";
+    private static String CONTEXT_PATH = "/foo";
+    private static String CLIENT_AUTH_HEADER = "authorization";
+    private static String REALM = "TestRealm";
+    private static String CNONCE = "cnonce";
+    private static String NC1 = "00000001";
+    private static String NC2 = "00000002";
+    private static String QOP = "auth";
+
+
+    @Test
+    public void bug54521() throws LifecycleException {
+        DigestAuthenticator digestAuthenticator = new DigestAuthenticator();
+        digestAuthenticator.setContainer(new TesterContext());
+        digestAuthenticator.start();
+        Request request = new TesterRequest();
+        final int count = 1000;
+
+        Set<String> nonces = new HashSet<>();
+
+        for (int i = 0; i < count; i++) {
+            nonces.add(digestAuthenticator.generateNonce(request));
+        }
+
+        Assert.assertEquals(count,  nonces.size());
+    }
+
+
+    @Test
+    public void testAllValid() throws Exception {
+        doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
+                NC1, NC2, CNONCE, QOP, true, true);
+    }
+
+    @Test
+    public void testValidNoQop() throws Exception {
+        doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
+                null, null, null, null, true, true);
+    }
+
+    @Test
+    public void testValidQuery() throws Exception {
+        doTest(USER, PWD, CONTEXT_PATH + URI + QUERY, false, true, REALM, true,
+                true, NC1, NC2, CNONCE, QOP, true, true);
+    }
+
+    @Test
+    public void testInvalidUriFail() throws Exception {
+        doTest(USER, PWD, CONTEXT_PATH + URI, true, true, REALM, true, true,
+                NC1, NC2, CNONCE, QOP, false, false);
+    }
+
+    @Test
+    @Ignore("URI validation is not implemented yet")
+    public void testInvalidUriPass() throws Exception {
+        doTest(USER, PWD, CONTEXT_PATH + URI, true, false, REALM, true, true,
+                NC1, NC2, CNONCE, QOP, true, true);
+    }
+
+    @Test
+    public void testInvalidRealm() throws Exception {
+        doTest(USER, PWD, CONTEXT_PATH + URI, false, true, "null", true, true,
+                NC1, NC2, CNONCE, QOP, false, false);
+    }
+
+    @Test
+    public void testInvalidNonce() throws Exception {
+        doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, false, true,
+                NC1, NC2, CNONCE, QOP, false, true);
+    }
+
+    @Test
+    public void testInvalidOpaque() throws Exception {
+        doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, false,
+                NC1, NC2, CNONCE, QOP, false, true);
+    }
+
+    @Test
+    public void testInvalidNc1() throws Exception {
+        doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
+                "null", null, CNONCE, QOP, false, false);
+    }
+
+    @Test
+    public void testInvalidQop() throws Exception {
+        doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
+                NC1, NC2, CNONCE, "null", false, false);
+    }
+
+    @Test
+    public void testInvalidQopCombo1() throws Exception {
+        doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
+                NC1, NC2, CNONCE, null, false, false);
+    }
+
+    @Test
+    public void testInvalidQopCombo2() throws Exception {
+        doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
+                NC1, NC2, null, QOP, false, false);
+    }
+
+    @Test
+    public void testInvalidQopCombo3() throws Exception {
+        doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
+                NC1, NC2, null, null, false, false);
+    }
+
+    @Test
+    public void testInvalidQopCombo4() throws Exception {
+        doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
+                null, null, CNONCE, QOP, false, false);
+    }
+
+    @Test
+    public void testInvalidQopCombo5() throws Exception {
+        doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
+                null, null, CNONCE, null, false, false);
+    }
+
+    @Test
+    public void testInvalidQopCombo6() throws Exception {
+        doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
+                null, null, null, QOP, false, false);
+    }
+
+    @Test
+    public void testReplay() throws Exception {
+        doTest(USER, PWD, CONTEXT_PATH + URI, false, true, REALM, true, true,
+                NC1, NC1, CNONCE, QOP, true, false);
+    }
+
+    public void doTest(String user, String pwd, String uri, boolean breakUri,
+            boolean validateUri, String realm, boolean useServerNonce,
+            boolean useServerOpaque, String nc1, String nc2, String cnonce,
+            String qop, boolean req2expect200, boolean req3expect200)
+            throws Exception {
+
+        if (!validateUri) {
+            DigestAuthenticator auth =
+                (DigestAuthenticator) getTomcatInstance().getHost().findChild(
+                        CONTEXT_PATH).getPipeline().getFirst();
+            auth.setValidateUri(false);
+        }
+        getTomcatInstance().start();
+
+        String digestUri;
+        if (breakUri) {
+            digestUri = "/broken" + uri;
+        } else {
+            digestUri = uri;
+        }
+        List<String> auth = new ArrayList<>();
+        auth.add(buildDigestResponse(user, pwd, digestUri, realm, "null",
+                "null", nc1, cnonce, qop));
+        Map<String,List<String>> reqHeaders = new HashMap<>();
+        reqHeaders.put(CLIENT_AUTH_HEADER, auth);
+
+        Map<String,List<String>> respHeaders = new HashMap<>();
+
+        // The first request will fail - but we need to extract the nonce
+        ByteChunk bc = new ByteChunk();
+        int rc = getUrl("http://localhost:"; + getPort() + uri, bc, reqHeaders,
+                respHeaders);
+        assertEquals(401, rc);
+        assertTrue(bc.getLength() > 0);
+        bc.recycle();
+
+        // Second request should succeed (if we use the server nonce)
+        auth.clear();
+        if (useServerNonce) {
+            if (useServerOpaque) {
+                auth.add(buildDigestResponse(user, pwd, digestUri, realm,
+                        getNonce(respHeaders), getOpaque(respHeaders), nc1,
+                        cnonce, qop));
+            } else {
+                auth.add(buildDigestResponse(user, pwd, digestUri, realm,
+                        getNonce(respHeaders), "null", nc1, cnonce, qop));
+            }
+        } else {
+            auth.add(buildDigestResponse(user, pwd, digestUri, realm,
+                    "null", getOpaque(respHeaders), nc1, cnonce, QOP));
+        }
+        rc = getUrl("http://localhost:"; + getPort() + uri, bc, reqHeaders,
+                null);
+
+        if (req2expect200) {
+            assertEquals(200, rc);
+            assertEquals("OK", bc.toString());
+        } else {
+            assertEquals(401, rc);
+            assertTrue(bc.getLength() > 0);
+        }
+
+        // Third request should succeed if we increment nc
+        auth.clear();
+        bc.recycle();
+        bc.reset();
+        auth.add(buildDigestResponse(user, pwd, digestUri, realm,
+                getNonce(respHeaders), getOpaque(respHeaders), nc2, cnonce,
+                qop));
+        rc = getUrl("http://localhost:"; + getPort() + uri, bc, reqHeaders,
+                null);
+
+        if (req3expect200) {
+            assertEquals(200, rc);
+            assertEquals("OK", bc.toString());
+        } else {
+            assertEquals(401, rc);
+            assertTrue(bc.getLength() > 0);
+        }
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // Configure a context with digest auth and a single protected resource
+        Tomcat tomcat = getTomcatInstance();
+
+        // No file system docBase required
+        Context ctxt = tomcat.addContext(CONTEXT_PATH, null);
+
+        // Add protected servlet
+        Tomcat.addServlet(ctxt, "TesterServlet", new TesterServlet());
+        ctxt.addServletMapping(URI, "TesterServlet");
+        SecurityCollection collection = new SecurityCollection();
+        collection.addPattern(URI);
+        SecurityConstraint sc = new SecurityConstraint();
+        sc.addAuthRole(ROLE);
+        sc.addCollection(collection);
+        ctxt.addConstraint(sc);
+
+        // Configure the Realm
+        TesterMapRealm realm = new TesterMapRealm();
+        realm.addUser(USER, PWD);
+        realm.addUserRole(USER, ROLE);
+        ctxt.setRealm(realm);
+
+        // Configure the authenticator
+        LoginConfig lc = new LoginConfig();
+        lc.setAuthMethod("DIGEST");
+        lc.setRealmName(REALM);
+        ctxt.setLoginConfig(lc);
+
+        AuthConfigFactory authConfigFactory = AuthConfigFactory.getFactory();
+        TomcatAuthConfigProvider provider = new TomcatAuthConfigProvider(ctxt);
+        authConfigFactory.registerConfigProvider(provider, 
JaspicAuthenticator.MESSAGE_LAYER,
+                null, "Tomcat Jaspic");
+        ctxt.getPipeline().addValve(new JaspicAuthenticator());
+    }
+
+
+    protected static String getNonce(Map<String,List<String>> respHeaders) {
+        List<String> authHeaders =
+            respHeaders.get(AuthenticatorBase.AUTH_HEADER_NAME);
+        // Assume there is only one
+        String authHeader = authHeaders.iterator().next();
+
+        int start = authHeader.indexOf("nonce=\"") + 7;
+        int end = authHeader.indexOf("\"", start);
+        return authHeader.substring(start, end);
+    }
+
+    protected static String getOpaque(Map<String,List<String>> respHeaders) {
+        List<String> authHeaders =
+            respHeaders.get(AuthenticatorBase.AUTH_HEADER_NAME);
+        // Assume there is only one
+        String authHeader = authHeaders.iterator().next();
+
+        int start = authHeader.indexOf("opaque=\"") + 8;
+        int end = authHeader.indexOf("\"", start);
+        return authHeader.substring(start, end);
+    }
+
+    /*
+     * Notes from RFC2617
+     * H(data) = MD5(data)
+     * KD(secret, data) = H(concat(secret, ":", data))
+     * A1 = unq(username-value) ":" unq(realm-value) ":" passwd
+     * A2 = Method ":" digest-uri-value
+     * request-digest  = <"> < KD ( H(A1),     unq(nonce-value)
+                                    ":" nc-value
+                                    ":" unq(cnonce-value)
+                                    ":" unq(qop-value)
+                                    ":" H(A2)
+                                   ) <">
+     */
+    private static String buildDigestResponse(String user, String pwd,
+            String uri, String realm, String nonce, String opaque, String nc,
+            String cnonce, String qop) {
+
+        String a1 = user + ":" + realm + ":" + pwd;
+        String a2 = "GET:" + uri;
+
+        String md5a1 = digest(a1);
+        String md5a2 = digest(a2);
+
+        String response;
+        if (qop == null) {
+            response = md5a1 + ":" + nonce + ":" + md5a2;
+        } else {
+            response = md5a1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" +
+                    qop + ":" + md5a2;
+        }
+
+        String md5response = digest(response);
+
+        StringBuilder auth = new StringBuilder();
+        auth.append("Digest username=\"");
+        auth.append(user);
+        auth.append("\", realm=\"");
+        auth.append(realm);
+        auth.append("\", nonce=\"");
+        auth.append(nonce);
+        auth.append("\", uri=\"");
+        auth.append(uri);
+        auth.append("\", opaque=\"");
+        auth.append(opaque);
+        auth.append("\", response=\"");
+        auth.append(md5response);
+        auth.append("\"");
+        if (qop != null) {
+            auth.append(", qop=");
+            auth.append(qop);
+            auth.append("");
+        }
+        if (nc != null) {
+            auth.append(", nc=");
+            auth.append(nc);
+        }
+        if (cnonce != null) {
+            auth.append(", cnonce=\"");
+            auth.append(cnonce);
+            auth.append("\"");
+        }
+
+        return auth.toString();
+    }
+
+    private static String digest(String input) {
+        return MD5Encoder.encode(
+                ConcurrentMessageDigest.digestMD5(input.getBytes()));
+    }
+
+
+    private static class TesterRequest extends Request {
+
+        @Override
+        public String getRemoteAddr() {
+            return "127.0.0.1";
+        }
+    }
+}

Propchange: 
tomcat/trunk/test/org/apache/catalina/authenticator/TestJaspicDigestAuthenticator.java
------------------------------------------------------------------------------
    svn:eol-style = native



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to