Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
idea/
.idea/
temp/
oie/
engine/
3 changes: 3 additions & 0 deletions .sdkmanrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
groovy=4.0.27
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Open Integration Engine release verification toolkit

This repo houses the scripts to verify Open integration Engine release validity.

The script downloads a packaged release tarball, and clones the [`engine`](https://github.com/OpenIntegrationEngine/engine) repository
at the specified commit.

Open Integration Engine is then built locally and a verification script then verifies all jar files either by comparing the full file SHA-256 hash
or by comparing the hashes of each file in a `.jar`.



## Requirements
1. [`sdkman`](https://sdkman.io/)
2. `bash`
3. Some Java already installed

## Usage
Run `./verify.sh <commit to verify against> <release url from github>`

`./verify.sh "788a150f36a6bcd1db672e00d2e7ee609e2842d9" "https://github.com/OpenIntegrationEngine/engine/releases/download/v4.5.2/oie_unix_4_5_2.tar.gz"`
190 changes: 190 additions & 0 deletions backend.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import groovy.io.FileType

import java.security.MessageDigest
import java.time.Duration
import java.time.Instant
import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.zip.ZipEntry

enum ValidationStrategy {
CLASS_DIGEST,
JAR_HASH,
COMPILATION,
SKIP,
}

final DIGEST_PREFIXES = ['oie/client-lib','oie/extensions']

def ensureFileIsDirectory(File dir) {
if (!dir.exists() || !dir.isDirectory()) {
println "Error: “${args[0]}” is not a directory"
System.exit(1)
}
}

static def tarPathToRepoPath(String path) {
return path
.replaceFirst('oie/', 'engine/')
.replaceFirst('server-lib/donkey', 'donkey/lib')
.replaceFirst('extensions', 'server/lib/extensions')
.replaceFirst('server-lib', 'server/lib')
.replaceFirst('client-lib', 'client/lib')
.replaceFirst('cli-lib', 'command/lib')
.replaceFirst('manager-lib', 'manager/lib')
}

static def calculateFileHash(String path) {
def md = MessageDigest.getInstance("SHA-256")

new File(path).withInputStream { is ->
byte[] buffer = new byte[8192]
int read
while((read = is.read(buffer)) != -1) {
md.update(buffer, 0, read)
}
}
return md.digest()
.collect { byte b -> String.format('%02x', b & 0xff) }
.join()
}

def handleJarHashStrategy(String tarPath, String repoPath) {
def tarJarHash = calculateFileHash(tarPath)
def repoJarHash = calculateFileHash(repoPath)

if (tarJarHash == repoJarHash) {
println("Hash verified for $tarPath - $tarJarHash")
} else {
println("""Hash verification failed for $tarPath
Expected: $tarJarHash
Calculated: $repoJarHash
""")
throw new RuntimeException("Hash verification failed for $tarPath")
}
}

static def getJarClassDigests(String tarPath) {
def result = new HashSet<Map.Entry<String, String>>()

new JarFile(tarPath).withCloseable { jar ->
jar.entries().findAll { e ->
!e.isDirectory() && e.name.endsWith('.class')
}.each { entry ->
def md = MessageDigest.getInstance('SHA-256')
jar.getInputStream(entry as ZipEntry).withCloseable { is ->
byte[] buffer = new byte[8192]
int read
while((read = is.read(buffer)) != -1) {
md.update(buffer, 0, read)
}
}
// convert bytes to hex
def hex = md.digest()
.collect { byte b -> String.format('%02x', b & 0xff) }
.join()

def basename = (entry as JarEntry).name
result.add(new AbstractMap.SimpleImmutableEntry<String, String>(basename, hex))
}
}
return result
}

def handleCompilationStrategy(String tarPath) {
def repoPath = tarPath.replace("oie/", "engine/server/setup/")
def tarJar = new JarFile(tarPath)

def hasClasses = tarJar.entries().iterator().any {
!it.name.startsWith("META-INF")
}

if (!hasClasses) {
// I don't know what to do here... skip?
println("$tarPath has no classes in it")
return
}

def tarDigests = getJarClassDigests(tarPath)
def repoDigests = getJarClassDigests(repoPath)

if (tarDigests == repoDigests) {
println("Class digests are equal for $tarPath")
} else {
throw new RuntimeException("Class digest verification failed for $tarPath")
}
}

def clonedRepoDir = new File("engine")
def tarballDir = new File("oie")
def tempDir = new File("temp")

ensureFileIsDirectory(clonedRepoDir)
ensureFileIsDirectory(tarballDir)
ensureFileIsDirectory(tempDir)

def startInstant = Instant.now()

def jarInTarToStrategyMap = new HashMap<String, ValidationStrategy>()

// Get all Jar files from the cloned git repository
tarballDir.traverse(
type: FileType.FILES,
nameFilter: ~/.*\.jar/
) { file ->
def strategy

// Ignore Install4j files
if (file.path.contains("install4j")) {
strategy = ValidationStrategy.SKIP

// Naive assumption that jars with no numbers in the name are compiled locally
} else if (file.name ==~ /.+[^0-9]\.jar/) {
strategy = ValidationStrategy.COMPILATION

// client-lib and extensions jars are signed
} else if (DIGEST_PREFIXES.any { file.path.startsWith(it) }) {
strategy = ValidationStrategy.CLASS_DIGEST

// Hash checks for everything else
} else {
strategy = ValidationStrategy.JAR_HASH
}

jarInTarToStrategyMap[file as String] = strategy
}

def initialFilesSet = jarInTarToStrategyMap.keySet()
def processedFilesSet = new HashSet<String>()

jarInTarToStrategyMap.each {
file = it.key
strategy = it.value

repoPath = tarPathToRepoPath(file)
println("-- Checking file $file with strategy $strategy")

if (strategy == ValidationStrategy.SKIP) {
return
} else if (strategy == ValidationStrategy.JAR_HASH) {
handleJarHashStrategy(file, repoPath)
processedFilesSet.add(file)
} else if (strategy == ValidationStrategy.CLASS_DIGEST || strategy == ValidationStrategy.COMPILATION) {
handleCompilationStrategy(file)
processedFilesSet.add(file)
}

return
}

def duration = Duration.between(startInstant, Instant.now())

println()
println("Initial files: ${initialFilesSet.size()}, processed files ${processedFilesSet.size()}")

println("Unprocessed files: ${initialFilesSet - processedFilesSet}")
println("If the unprocessed files list contains install4j jars only then you're most likely good already")
println()


println("Time elapsed: ${duration.toSeconds()} seconds")
44 changes: 44 additions & 0 deletions verify.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
export SDKMAN_DIR="${HOME}/.sdkman"
[[ -s "${SDKMAN_DIR}/bin/sdkman-init.sh" ]] && source "${SDKMAN_DIR}/bin/sdkman-init.sh"

############################################
#
# Configure the release and commit hash here
#
############################################

# example "788a150f36a6bcd1db672e00d2e7ee609e2842d9"
COMMIT_HASH=$1

# example "https://github.com/OpenIntegrationEngine/engine/releases/download/v4.5.2/oie_unix_4_5_2.tar.gz
DOWNLOAD_URL=$2

# cleanup
rm -rf engine oie temp
mkdir oie engine temp

curl -L -o temp/oie.tar.gz "$DOWNLOAD_URL"
tar xzf temp/oie.tar.gz -C .

# Clone engine repository at
git clone --depth=1 --revision="$COMMIT_HASH" git@github.com:OpenIntegrationEngine/engine.git engine

# Get correct groovy
sdk env install

pushd engine

# Get correct java and ant
sdk env install

pushd server
# Build without signing - is faster
ant clean
ant -f mirth-build.xml -DdisableSigning=true

# Drop out of dir stack
popd
popd

groovy backend.groovy