From 00ad70b3bd7f8859c710039857aa7da17a29b3d7 Mon Sep 17 00:00:00 2001 From: Sven Gothel Date: Wed, 3 Apr 2019 06:04:52 +0200 Subject: Bug 1369: Source Certification Contract (SCC): Initial SHA256 fingerprint & runtime validation This change implements a strong SHA256 signature over: 1) source tree inclusive make recipe (SHA256-Source) 2) all class files (SHA256-Classes) 3) all native libraries (SHA256-Natives) 4) the class files as deployed in the jar (SHA256-Classes-this) 5) the native libraries as deployed in the jar (SHA256-Natives-this) and drops all of these in the deployed Jar file. This allows SHA256 validation of (4) + (5) at runtime and further complete validation (1), (2) and (3) offline. Full SCC would now required (1) - (3) to be placed on a server for further validation. Optionally we may use GPG or PGP to validate the build entity to implement the chain of trust The SHA256 runtime validation is tested via: com.jogamp.common.util.TestVersionInfo --- src/java/com/jogamp/common/util/SHASum.java | 330 ++++++++++++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 src/java/com/jogamp/common/util/SHASum.java (limited to 'src/java/com/jogamp/common/util/SHASum.java') diff --git a/src/java/com/jogamp/common/util/SHASum.java b/src/java/com/jogamp/common/util/SHASum.java new file mode 100644 index 0000000..6489812 --- /dev/null +++ b/src/java/com/jogamp/common/util/SHASum.java @@ -0,0 +1,330 @@ +/** + * Copyright 2019 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ + +package com.jogamp.common.util; + +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.regex.Pattern; + +import com.jogamp.common.GlueGenVersion; +import com.jogamp.common.util.cache.TempFileCache; +import com.jogamp.common.util.cache.TempJarCache; + +import jogamp.common.Debug; + +/** + * Utility class to produce secure hash (SHA) sums over diverse input sources. + *

+ * See {@link #updateDigest(MessageDigest, List)} + *

+ *

+ * This implementation is being utilized at JogAmp build time to produce various + * SHA256 sums over sources, class files and native libraries to ensure their identity. + * See {@link JogampVersion#getImplementationSHA256Sources()}, + * {@link JogampVersion#getImplementationSHA256Classes()} + * and {@link JogampVersion#getImplementationSHA256Natives()}. + *

+ *

+ * {@link JogampVersion#getImplementationSHA256Sources()} for module gluegen is produced via: + *

+ * java -cp build/gluegen-rt.jar com.jogamp.common.util.SHASum --algorithm 256 --exclude ".*\\.log" --exclude "make/lib/toolchain" src jcpp/src make
+ * 
+ *

+ * @see #SHASum(MessageDigest, List, List, List) + * @see #compute(boolean) + * @see TempJarSHASum + * @see #main(String[]) + */ +public class SHASum { + private static final boolean DEBUG = Debug.debug("SHASum"); + + /** + * {@link MessageDigest#update(byte[], int, int) Updates} the given {@code digest} + * with the bytes contained by the files denoted by the given {@code filenames} in the given order. + *

+ * To retrieve the list of all files traversing through directories, one may use {@link IOUtil#filesOf(List, List, List)}. + *

+ *

+ * The SHA implementation is sensitive to the order of input bytes and hence the given filename order. + *

+ *

+ * It is advised to pass given list of filenames in lexicographically sorted order to ensure reproducible outcome across all platforms, + * one may use {@link #sort(ArrayList)}. + *

+ *

+ * As an example, one could write + *

+     * final MessageDigest digest = ...;
+     * final long totalBytes = updateDigest(digest, sort(IOUtil.filesOf(Arrays.asList("sources"), null, null)));
+     * 
+ *

+ * @param digest to be updated digest + * @param filenames list of filenames denoting files, which bytes will be used to update the digest + * @return total number of bytes read. + * @throws FileNotFoundException see {@link FileInputStream#FileInputStream(String)} + * @throws IOException see {@link InputStream#read(byte[])} + */ + public static long updateDigest(final MessageDigest digest, final List filenames) throws IOException { + long numBytes = 0; + final byte buffer[] = new byte[4096]; // avoid Platform.getMachineDataInfo().pageSizeInBytes() due to native dependency + for(int i=0; i sort(final ArrayList source) { + final String s[] = source.toArray(new String[source.size()]); + Arrays.sort(s, 0, s.length, null); + return Arrays.asList(s); + } + + final MessageDigest digest; + final List origins; + final List excludes, includes; + + /** + * Instance to ensure proper {@link #compute(boolean)} of identical SHA sums over same contents within given paths across machines. + *

+ * Instantiation of this class is lightweight, {@link #compute(boolean)} performs all operations. + *

+ * + * @param digest the SHA algorithm + * @param origins the mandatory path origins to be used for {@link IOUtil#filesOf(List, List, List)} + * @param excludes the optional exclude patterns to be used for {@link IOUtil#filesOf(List, List, List)} + * @param includes the optional include patterns to be used for {@link IOUtil#filesOf(List, List, List)} + * @throws IllegalArgumentException + * @throws IOException + * @throws URISyntaxException + */ + public SHASum(final MessageDigest digest, final List origins, final List excludes, final List includes) { + this.digest = digest; + this.origins = origins; + this.excludes = excludes; + this.includes = includes; + } + + /** + * Implementation gathers all files traversing through given paths via {@link IOUtil#filesOf(List, List, List)}, + * sorts the resulting file list via {@link #sort(ArrayList)} and finally + * calculates the SHA sum over its byte content via {@link #updateDigest(MessageDigest, List)}. + *

+ * This ensures identical SHA sums over same contents within given paths across machines. + *

+ *

+ * This method is heavyweight and performs all operations. + *

+ * + * @param verbose if true, all used files will be dumped as well as the digest result + * @return the resulting SHA value + * @throws IOException + */ + public final byte[] compute(final boolean verbose) throws IOException { + final List fnamesS = SHASum.sort(IOUtil.filesOf(origins, excludes, includes)); + if( verbose ) { + for(int i=0; i getOrigins() { return origins; } + public final List getExcludes() { return excludes; } + public final List getIncludes() { return includes; } + + /** + * {@link SHASum} specialization utilizing {@link TempJarCache} to access jar file content for SHA computation + */ + public static class TempJarSHASum extends SHASum { + /** + * Instance to ensure proper {@link #compute(boolean)} of identical SHA sums over same contents within given paths across machines. + *

+ * Instantiation of this class is lightweight, {@link #compute(boolean)} performs all operations. + *

+ *

+ * {@link TempJarCache#getTempFileCache()}'s {@link TempFileCache#getTempDir()} is used as origin for {@link IOUtil#filesOf(List, List, List)} + *

+ * + * @param digest the SHA algorithm + * @param jarclazz a class from the desired classpath jar file used for {@link TempJarCache#addAll(Class, com.jogamp.common.net.Uri)} + * @param excludes the optional exclude patterns to be used for {@link IOUtil#filesOf(List, List, List)} + * @param includes the optional include patterns to be used for {@link IOUtil#filesOf(List, List, List)} + * @throws SecurityException + * @throws IllegalArgumentException + * @throws IOException + * @throws URISyntaxException + */ + public TempJarSHASum(final MessageDigest digest, final Class jarclazz, final List excludes, final List includes) + throws SecurityException, IllegalArgumentException, IOException, URISyntaxException + { + super(digest, Arrays.asList(IOUtil.slashify(TempJarCache.getTempFileCache().getTempDir().getAbsolutePath(), false, false)), + excludes, includes); + TempJarCache.addAll(jarclazz, JarUtil.getJarFileUri(jarclazz.getName(), jarclazz.getClassLoader())); + } + + public final String getOrigin() { return origins.get(0); } + } + + /** + * Main entry point taking var-arg path or gnu-arguments with a leading '--'. + *

+ * Implementation gathers all files traversing through given paths via {@link IOUtil#filesOf(List, List, List)}, + * sorts the resulting file list via {@link #sort(ArrayList)} and finally + * calculates the SHA sum over its byte content via {@link #updateDigest(MessageDigest, List)}. + * This ensures identical SHA sums over same contents within given paths. + *

+ *

+ * Example to calculate the SHA-256 over our source files as performed for {@link JogampVersion#getImplementationSHA256Sources()} + *

+     * java -cp build/gluegen-rt.jar com.jogamp.common.util.SHASum --algorithm 256 --exclude ".*\\.log" --exclude "make/lib/toolchain" src jcpp/src make
+     * 
+ *

+ *

+ * To validate the implementation, one can gather the sorted list of files (to ensure same order) + *

+     * java -cp build/gluegen-rt.jar com.jogamp.common.util.SHASum --listfilesonly --exclude ".*\\.log" --exclude "make/lib/toolchain" src jcpp/src make >& java.sorted.txt
+     * 
+ * and then calculate the shasum independently + *
+     * find `cat java.sorted.txt` -exec cat {} + | shasum -a 256 -b - | awk '{print $1}'
+     * 
+ *

+ * @param args + * @throws IOException + * @throws URISyntaxException + * @throws IllegalArgumentException + */ + public static void main(final String[] args) throws IOException { + boolean listFilesOnly = false; + int shabits = 256; + int i; + final ArrayList pathU = new ArrayList(); + final ArrayList excludes = new ArrayList(); + final ArrayList includes = new ArrayList(); + { + for(i=0; i -> <"+excludes.get(excludes.size()-1)+">"); + } + } else if( args[i].equals("--include")) { + includes.add(Pattern.compile(args[++i])); + if( DEBUG ) { + System.err.println("adding include: <"+args[i]+"> -> <"+includes.get(includes.size()-1)+">"); + } + } else if( args[i].equals("--listfilesonly")) { + listFilesOnly = true; + } else { + System.err.println("Abort, unknown argument: "+args[i]); + return; + } + } else { + pathU.add(args[i]); + if( DEBUG ) { + System.err.println("adding path: <"+args[i]+">"); + } + } + } + } + if( listFilesOnly ) { + final List fnamesS = sort(IOUtil.filesOf(pathU, excludes, includes)); + for(i=0; i