You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by sv...@apache.org on 2020/01/09 07:01:46 UTC

[wicket] branch master updated: WICKET-6321 dynamic SRI integrity calculation

This is an automated email from the ASF dual-hosted git repository.

svenmeier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/wicket.git


The following commit(s) were added to refs/heads/master by this push:
     new 5fb92a7  WICKET-6321 dynamic SRI integrity calculation
5fb92a7 is described below

commit 5fb92a71e28e22e0d358ddb4b7a36b97207339f2
Author: Sven Meier <sv...@apache.org>
AuthorDate: Wed Jan 8 09:18:04 2020 +0100

    WICKET-6321 dynamic SRI integrity calculation
---
 .../examples/sri/DynamicSubresourceIntegrity.java  | 169 +++++++++++++++++++++
 .../wicket/examples/sri/IntegrityDemoPage.html     |   5 +-
 .../wicket/examples/sri/IntegrityDemoPage.java     |   5 +
 .../apache/wicket/examples/sri/SriApplication.java |  25 +--
 .../sri/{subresource.js => subresource.css}        |  14 +-
 .../org/apache/wicket/examples/sri/subresource.js  |   7 +-
 6 files changed, 188 insertions(+), 37 deletions(-)

diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/sri/DynamicSubresourceIntegrity.java b/wicket-examples/src/main/java/org/apache/wicket/examples/sri/DynamicSubresourceIntegrity.java
new file mode 100644
index 0000000..3d92501
--- /dev/null
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/sri/DynamicSubresourceIntegrity.java
@@ -0,0 +1,169 @@
+/*
+ * 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.wicket.examples.sri;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+import java.util.Base64.Encoder;
+import java.util.Map;
+
+import org.apache.commons.collections4.map.HashedMap;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.IReferenceHeaderItem;
+import org.apache.wicket.markup.head.ISubresourceHeaderItem;
+import org.apache.wicket.markup.head.filter.SubresourceHeaderResponse;
+import org.apache.wicket.request.Url;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.resource.IResource;
+import org.apache.wicket.request.resource.ResourceReference;
+import org.apache.wicket.request.resource.caching.IStaticCacheableResource;
+import org.apache.wicket.util.io.IOUtils;
+import org.apache.wicket.util.resource.IResourceStream;
+import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Dynamic calculation of SRI for {@link IStaticCacheableResource}s.
+ * 
+ * @author svenmeier
+ */
+public class DynamicSubresourceIntegrity
+{
+	private static final Logger log = LoggerFactory.getLogger(DynamicSubresourceIntegrity.class);
+
+	private Map<Serializable, String> cache = new HashedMap<>();
+
+	/**
+	 * Wrap the given response
+	 * 
+	 * @param response
+	 *            response to add SRI to
+	 * @return wrapper
+	 */
+	public IHeaderResponse wrap(IHeaderResponse response)
+	{
+		return new SubresourceHeaderResponse(response)
+		{
+			@Override
+			protected void configure(ISubresourceHeaderItem item)
+			{
+				String integrity = getIntegrity(item);
+				if (integrity != null)
+				{
+					item.setIntegrity(integrity);
+				}
+			}
+		};
+	}
+
+	public String getIntegrity(ISubresourceHeaderItem item)
+	{
+		if (item instanceof IReferenceHeaderItem)
+		{
+			ResourceReference reference = ((IReferenceHeaderItem)item).getReference();
+
+			IResource resource = reference.getResource();
+			if (resource instanceof IStaticCacheableResource)
+			{
+				IStaticCacheableResource cacheableResource = (IStaticCacheableResource)resource;
+				
+				return getIntegrity(reference, cacheableResource);
+			}
+		}
+
+		return null;
+	}
+
+	private String getIntegrity(ResourceReference reference, IStaticCacheableResource cacheableResource)
+	{
+		String integrity = cache.get(cacheableResource.getCacheKey());
+		if (integrity == null)
+		{
+			Url baseUrl = getBaseUrl(reference);
+			try
+			{
+				byte[] bytes = getBytes(cacheableResource, baseUrl);
+
+				integrity = "sha384-" + createHash(bytes);
+				cache.put(cacheableResource.getCacheKey(), integrity);
+			}
+			catch (Exception ex)
+			{
+				log.error("cannot calculate integrity", ex);
+			}
+		}
+		return integrity;
+	}
+
+	private Url getBaseUrl(ResourceReference reference)
+	{
+		RequestCycle cycle = RequestCycle.get();
+		Url url = Url.parse(cycle.urlFor(reference, null));
+		if (url.getSegments().get(0).equals("."))
+		{
+			// not sure why this is needed but leading dot must be removed,
+			// otherwise relative urls will differ from the actually served css
+			url.removeLeadingSegments(1);
+		}
+		
+		return url;
+	}
+
+	/**
+	 * Get bytes.
+	 */
+	protected byte[] getBytes(IStaticCacheableResource cacheableResource, Url baseUrl)
+		throws IOException, ResourceStreamNotFoundException
+	{
+		byte[] bytes;
+
+		// base url has to be adjusted for relative images in CSS
+		RequestCycle cycle = RequestCycle.get();
+		Url originalBaseUrl = cycle.getUrlRenderer().setBaseUrl(baseUrl);
+
+		try (IResourceStream stream = cacheableResource.getResourceStream())
+		{
+			bytes = IOUtils.toByteArray(stream.getInputStream());
+		}
+		finally
+		{
+			cycle.getUrlRenderer().setBaseUrl(originalBaseUrl);
+		}
+
+		return bytes;
+	}
+
+	/**
+	 * Create the hash.
+	 * 
+	 * <pre>
+	 * openssl dgst -sha384 -binary xy.js | openssl base64 -A
+	 * </pre>
+	 */
+	protected String createHash(byte[] bytes) throws NoSuchAlgorithmException
+	{
+		MessageDigest digest = MessageDigest.getInstance("SHA-384");
+
+		Encoder encoder = Base64.getEncoder();
+
+		return encoder.encodeToString(digest.digest(bytes));
+	}
+}
\ No newline at end of file
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/sri/IntegrityDemoPage.html b/wicket-examples/src/main/java/org/apache/wicket/examples/sri/IntegrityDemoPage.html
index 37815c5..3204a4e 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/sri/IntegrityDemoPage.html
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/sri/IntegrityDemoPage.html
@@ -6,7 +6,10 @@
 <wicket:extend>
     <h3>SRI Demo</h3>
     
-    <p id="sri">Integrity test NOT passed</p>
+    <div id="sri">
+	    <p class="sri-false" style="color: red;">Integrity test NOT passed</p>
+	    <p class="sri-true" style="color: green; font-weight: bold; display: none;">Integrity test passed</p>
+    </div> 
 </wicket:extend>
 </body>
 </html>
\ No newline at end of file
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/sri/IntegrityDemoPage.java b/wicket-examples/src/main/java/org/apache/wicket/examples/sri/IntegrityDemoPage.java
index 0149275..e61b5fe 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/sri/IntegrityDemoPage.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/sri/IntegrityDemoPage.java
@@ -17,6 +17,7 @@
 package org.apache.wicket.examples.sri;
 
 import org.apache.wicket.examples.WicketExamplePage;
+import org.apache.wicket.markup.head.CssHeaderItem;
 import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.markup.head.JavaScriptHeaderItem;
 import org.apache.wicket.request.resource.JavaScriptResourceReference;
@@ -31,6 +32,9 @@ public class IntegrityDemoPage extends WicketExamplePage
 	public static final ResourceReference JS = new JavaScriptResourceReference(
 		IntegrityDemoPage.class, "subresource.js");
 
+	public static final ResourceReference CSS = new JavaScriptResourceReference(
+		IntegrityDemoPage.class, "subresource.css");
+
 	public IntegrityDemoPage()
 	{
 	}
@@ -41,5 +45,6 @@ public class IntegrityDemoPage extends WicketExamplePage
 		super.renderHead(response);
 
 		response.render(JavaScriptHeaderItem.forReference(JS));
+		response.render(CssHeaderItem.forReference(CSS));
 	}
 }
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/sri/SriApplication.java b/wicket-examples/src/main/java/org/apache/wicket/examples/sri/SriApplication.java
index b7ae7ab..8b9a4f4 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/sri/SriApplication.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/sri/SriApplication.java
@@ -18,15 +18,12 @@ package org.apache.wicket.examples.sri;
 
 import org.apache.wicket.Page;
 import org.apache.wicket.examples.WicketExampleApplication;
-import org.apache.wicket.markup.head.ISubresourceHeaderItem;
-import org.apache.wicket.markup.head.JavaScriptReferenceHeaderItem;
 import org.apache.wicket.markup.head.ResourceAggregator;
-import org.apache.wicket.markup.head.filter.SubresourceHeaderResponse;
-import org.apache.wicket.request.resource.JavaScriptResourceReference;
-import org.apache.wicket.request.resource.ResourceReference;
 
 public class SriApplication extends WicketExampleApplication
 {
+	private DynamicSubresourceIntegrity integrity = new DynamicSubresourceIntegrity();
+
 	@Override
 	public Class<? extends Page> getHomePage()
 	{
@@ -38,23 +35,7 @@ public class SriApplication extends WicketExampleApplication
 	{
 		super.init();
 
-		setHeaderResponseDecorator(
-			response -> new ResourceAggregator(new SubresourceHeaderResponse(response)
-			{
-				@Override
-				protected void configure(ISubresourceHeaderItem item)
-				{
-					if (item instanceof JavaScriptReferenceHeaderItem) {
-						ResourceReference reference = ((JavaScriptReferenceHeaderItem)item).getReference();
-						
-						if (reference.equals(IntegrityDemoPage.JS)) {
-							String algorithm = "sha384";
-							String value = "yDSj1gWA4teUdCx2/5M0RsK1jovKR0RdUeeLXKU1gRpNWevoQDGhjHEd1R6Jb+FQ";
-							item.setIntegrity(algorithm + "-" + value);
-						}
-					}
-				}
-			}));
+		setHeaderResponseDecorator(response -> new ResourceAggregator(integrity.wrap(response)));
 
 		mountPage("integritydemo", IntegrityDemoPage.class);
 	}
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/sri/subresource.js b/wicket-examples/src/main/java/org/apache/wicket/examples/sri/subresource.css
similarity index 71%
copy from wicket-examples/src/main/java/org/apache/wicket/examples/sri/subresource.js
copy to wicket-examples/src/main/java/org/apache/wicket/examples/sri/subresource.css
index e0e1fa6..e6718e3 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/sri/subresource.js
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/sri/subresource.css
@@ -14,11 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/**
- * For any change in this file its hash has to be updated in SriApplication:
- * 
- * openssl dgst -sha384 -binary subresource.js | openssl base64 -A 
- */
-document.addEventListener('DOMContentLoaded', function(event) {
-	document.querySelector("#sri").textContent = 'Integrity test passed'; 
-});
\ No newline at end of file
+.sri-passed .sri-false {
+	display: none;
+}
+.sri-passed .sri-true {
+	display: block !important;
+}
\ No newline at end of file
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/sri/subresource.js b/wicket-examples/src/main/java/org/apache/wicket/examples/sri/subresource.js
index e0e1fa6..ee130b1 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/sri/subresource.js
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/sri/subresource.js
@@ -14,11 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/**
- * For any change in this file its hash has to be updated in SriApplication:
- * 
- * openssl dgst -sha384 -binary subresource.js | openssl base64 -A 
- */
 document.addEventListener('DOMContentLoaded', function(event) {
-	document.querySelector("#sri").textContent = 'Integrity test passed'; 
+	document.querySelector("#sri").classList.add("sri-passed"); 
 });
\ No newline at end of file