You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ie...@apache.org on 2013/08/15 20:02:06 UTC

svn commit: r1514417 - in /sling/whiteboard/ieb/sealed: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/sling/ src/main/java/org/apache/sling/sealed/

Author: ieb
Date: Thu Aug 15 18:02:06 2013
New Revision: 1514417

URL: http://svn.apache.org/r1514417
Log:
Experimenting with sealing requests and responses with a hmac

Added:
    sling/whiteboard/ieb/sealed/
    sling/whiteboard/ieb/sealed/pom.xml   (with props)
    sling/whiteboard/ieb/sealed/src/
    sling/whiteboard/ieb/sealed/src/main/
    sling/whiteboard/ieb/sealed/src/main/java/
    sling/whiteboard/ieb/sealed/src/main/java/org/
    sling/whiteboard/ieb/sealed/src/main/java/org/apache/
    sling/whiteboard/ieb/sealed/src/main/java/org/apache/sling/
    sling/whiteboard/ieb/sealed/src/main/java/org/apache/sling/sealed/
    sling/whiteboard/ieb/sealed/src/main/java/org/apache/sling/sealed/SealedResponse.java   (with props)
    sling/whiteboard/ieb/sealed/src/main/java/org/apache/sling/sealed/SealingFilter.java   (with props)

Added: sling/whiteboard/ieb/sealed/pom.xml
URL: http://svn.apache.org/viewvc/sling/whiteboard/ieb/sealed/pom.xml?rev=1514417&view=auto
==============================================================================
--- sling/whiteboard/ieb/sealed/pom.xml (added)
+++ sling/whiteboard/ieb/sealed/pom.xml Thu Aug 15 18:02:06 2013
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>16</version>
+    </parent>
+
+    <artifactId>org.apache.sling.sealed</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <name>Apache Sling Sealer</name>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/whiteboard/ieb/sealed</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/whiteboard/ieb/sealed</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/whiteboard/ieb/sealed</url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>
+                        </Export-Package>
+                        <Private-Package>
+                            org.apache.sling.sealed.*,
+                        </Private-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <version>2.0.8</version>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+             <groupId>commons-io</groupId>
+             <artifactId>commons-io</artifactId>
+             <version>2.4</version>
+        </dependency>
+        <dependency>
+             <groupId>commons-codec</groupId>
+             <artifactId>commons-codec</artifactId>
+             <version>1.5</version>
+        </dependency>
+        <!-- Testing -->
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.testing</artifactId>
+            <version>2.0.4-incubator</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+        </dependency>
+    </dependencies>
+</project>

Propchange: sling/whiteboard/ieb/sealed/pom.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/whiteboard/ieb/sealed/pom.xml
------------------------------------------------------------------------------
    svn:mime-type = text/xml

Added: sling/whiteboard/ieb/sealed/src/main/java/org/apache/sling/sealed/SealedResponse.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/ieb/sealed/src/main/java/org/apache/sling/sealed/SealedResponse.java?rev=1514417&view=auto
==============================================================================
--- sling/whiteboard/ieb/sealed/src/main/java/org/apache/sling/sealed/SealedResponse.java (added)
+++ sling/whiteboard/ieb/sealed/src/main/java/org/apache/sling/sealed/SealedResponse.java Thu Aug 15 18:02:06 2013
@@ -0,0 +1,108 @@
+/*
+ * 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 SF 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.sling.sealed;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.wrappers.SlingHttpServletResponseWrapper;
+
+/**
+ * Captures a response, and makes part of it available for signing when the response is sent.
+ */
+public class SealedResponse extends SlingHttpServletResponseWrapper {
+
+    private ServletOutputStream outputStream;
+    private ByteArrayOutputStream byteOutputStream;
+    private PrintWriter writer;
+    private Map<String, String> headers = new HashMap<String, String>();
+
+    public SealedResponse(ServletRequest request, ServletResponse response) {
+        super(getResponseToWrap(response));
+    }
+
+    private static SlingHttpServletResponse getResponseToWrap(ServletResponse response) {
+        try {
+            return (SlingHttpServletResponse) response;
+        } catch ( ClassCastException e ) {
+            throw new IllegalArgumentException();
+        }
+    }
+    
+    @Override
+    public void addHeader(String name, String value) {
+        headers.put(name, value);
+        super.addHeader(name, value);
+    }
+    
+    @Override
+    public void setHeader(String name, String value) {
+        headers.put(name, value);
+        super.setHeader(name, value);
+    }
+    
+    @Override
+    public ServletOutputStream getOutputStream() throws IOException {
+        if ( writer != null ) {
+            throw new IllegalStateException("Writer has been retrieved already");
+        }
+        if ( outputStream == null ) {
+            byteOutputStream = new ByteArrayOutputStream();
+            outputStream = new ServletOutputStream() {
+                
+                @Override
+                public void write(int arg0) throws IOException {
+                    byteOutputStream.write(arg0);
+                }
+            };
+        }
+        return outputStream;
+    }
+    
+    @Override
+    public PrintWriter getWriter() throws IOException {
+        if ( outputStream != null ) {
+            throw new IllegalStateException("OutputStream has been retrieved already");
+        }
+        if ( writer == null ) {
+            byteOutputStream = new ByteArrayOutputStream();
+            writer = new PrintWriter(byteOutputStream);
+        }
+        return writer;
+    }
+
+    /**
+     * Seal and commit the response.
+     * @param sealingFilter
+     * @throws IOException
+     */
+    public void seal(SealingFilter sealingFilter) throws IOException {
+        super.setHeader(SealingFilter.X_SIG_HEADER, sealingFilter.getHmac(byteOutputStream, headers));
+        super.getOutputStream().write(byteOutputStream.toByteArray());
+    }
+
+
+}

Propchange: sling/whiteboard/ieb/sealed/src/main/java/org/apache/sling/sealed/SealedResponse.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: sling/whiteboard/ieb/sealed/src/main/java/org/apache/sling/sealed/SealingFilter.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/ieb/sealed/src/main/java/org/apache/sling/sealed/SealingFilter.java?rev=1514417&view=auto
==============================================================================
--- sling/whiteboard/ieb/sealed/src/main/java/org/apache/sling/sealed/SealingFilter.java (added)
+++ sling/whiteboard/ieb/sealed/src/main/java/org/apache/sling/sealed/SealingFilter.java Thu Aug 15 18:02:06 2013
@@ -0,0 +1,252 @@
+/*
+ * 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 SF 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.sling.sealed;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.IOUtils;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+
+@Service(value = Filter.class)
+@Component(immediate = true, metatype = true)
+@Properties(@Property(name = "path", value = "/sealed"))
+public class SealingFilter implements Filter {
+
+    public static final String X_SIG_HEADER = "x-sig";
+
+    private static final String HMAC_ALG = "HmacSHA256";
+
+    @Property(cardinality = Integer.MAX_VALUE)
+    private static final String PROP_PATTERNS = "sealed-paths";
+
+    @Property(cardinality= Integer.MAX_VALUE)
+    private static final String PROP_HEADERNAMES = "sealed-headers";
+
+    @Property
+    private static final String PROP_SHARED_KEY = "shared-key";
+    
+    
+
+    private Pattern[] pathPatterns;
+
+    private String[] headerNames;
+
+    private Key key;
+
+    public void init(FilterConfig filterConfig) throws ServletException {
+    }
+    
+    @Activate
+    public void activate(Map<String, Object> properties) {
+        pathPatterns = buildPatterns((String[]) properties.get(PROP_PATTERNS));
+        headerNames = buildHeaderNames((String[]) properties.get(PROP_HEADERNAMES));
+        key = buildKey((String) properties.get(PROP_SHARED_KEY));
+    }
+
+    private Key buildKey(String sharedKey) {
+        if ( sharedKey != null ) { 
+            return new SecretKeySpec(sharedKey.getBytes(), HMAC_ALG);
+        }
+        return null;
+        
+    }
+
+    private String[] buildHeaderNames(String[] names) {
+        if ( names != null ) {
+            return names;
+        }
+        return new String[0];
+    }
+
+    private Pattern[] buildPatterns(String[] patterns) {
+        if (patterns != null) {
+            Pattern[] p = new Pattern[patterns.length];
+            for ( int i =0;i< p.length; i++ ) {
+                p[i] = Pattern.compile(patterns[i]);
+            }
+            return p;
+        }
+        return new Pattern[0];
+    }
+
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+            throws IOException, ServletException {
+        if (!verify(request) && response instanceof HttpServletResponse) {
+            ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN,
+                "Untrusted request");
+        } else {
+            if (secure(request)) {
+                SealedResponse sealedResponse = new SealedResponse(request, response);
+                chain.doFilter(request, sealedResponse);
+                sealedResponse.seal(this);
+            } else {
+                chain.doFilter(request, response);
+            }
+        }
+    }
+
+    /**
+     * Should the request and response be secure and trusted ?
+     * 
+     * @param request
+     * @return
+     */
+    private boolean secure(ServletRequest request) {
+        if (key != null && request instanceof HttpServletRequest) {
+            return match(((HttpServletRequest) request).getRequestURI(), pathPatterns);
+        }
+        return false;
+    }
+
+    /**
+     * Verify that the trusted parts of the servlet request have not been
+     * tampered with.
+     * 
+     * @param request
+     * @return true if the request has not been tampered with.
+     * @throws IOException
+     */
+    private boolean verify(ServletRequest request) throws IOException {
+        if (request instanceof HttpServletRequest) {
+            HttpServletRequest hrequest = (HttpServletRequest) request;
+            if (secure(hrequest)) {
+                return verifyHmac(buildMessage(hrequest), hrequest.getHeader(X_SIG_HEADER));
+            }
+        }
+        return true;
+    }
+
+    /**
+     * verify a message against a hmac.
+     * 
+     * @param message the message
+     * @param hmac the hmac
+     * @return true if the message hasn't been tampered with and the hmac was
+     *         created with the same key.
+     */
+    private boolean verifyHmac(String message, String hmac) {
+        String newMac = doMac(message);
+        return (hmac != null && newMac != null && !hmac.equals(newMac));
+    }
+
+    /**
+     * Perform a hmac on a message.
+     * 
+     * @param message the message
+     * @return the hmac of the message or null if the hmac cant be computed.
+     */
+    private String doMac(String message) {
+        try {
+            Mac mac = Mac.getInstance(HMAC_ALG);
+            mac.init(key);
+            return new String(Base64.encodeBase64(mac.doFinal(message.getBytes("UTF-8"))), "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+        } catch (IllegalStateException e) {
+        } catch (NoSuchAlgorithmException e) {
+        } catch (InvalidKeyException e) {
+        }
+        return null;
+    }
+
+    /**
+     * Build a message from the request
+     * 
+     * @param request
+     * @return
+     * @throws IOException
+     */
+    private String buildMessage(HttpServletRequest request) throws IOException {
+        StringBuilder message = new StringBuilder();
+        for (String headerName : headerNames) {
+            message.append(request.getHeader(headerName));
+        }
+        ServletInputStream in = request.getInputStream();
+        in.mark(1024 * 1024);
+        message.append(new String(Base64.encodeBase64(IOUtils.toByteArray(in)), "UTF-8"));
+        in.reset();
+        // not certain what will happen if I close in
+        in.close();
+        return message.toString();
+    }
+
+    private String buildMesssage(ByteArrayOutputStream byteOutputStream, Map<String, String> headers)
+            throws UnsupportedEncodingException {
+        StringBuilder message = new StringBuilder();
+        for (String headerName : headerNames) {
+            message.append(headers.get(headerName));
+        }
+        message.append(new String(Base64.encodeBase64(byteOutputStream.toByteArray()), "UTF-8"));
+        return message.toString();
+    }
+
+    /**
+     * return true if the test matches any of the patterns.
+     * 
+     * @param test
+     * @param patterns
+     * @return
+     */
+    private boolean match(String test, Pattern[] patterns) {
+        for (Pattern urls : patterns) {
+            if (urls.matcher(test).find()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void destroy() {
+    }
+
+    /**
+     * Get a hmac from a captured response.
+     * @param byteOutputStream
+     * @param headers
+     * @return
+     * @throws UnsupportedEncodingException
+     */
+    public String getHmac(ByteArrayOutputStream byteOutputStream, Map<String, String> headers)
+            throws UnsupportedEncodingException {
+        return doMac(buildMesssage(byteOutputStream, headers));
+    }
+
+}

Propchange: sling/whiteboard/ieb/sealed/src/main/java/org/apache/sling/sealed/SealingFilter.java
------------------------------------------------------------------------------
    svn:eol-style = native