diff --git a/.github/workflows/java-module-test.yml b/.github/workflows/java-module-test.yml new file mode 100644 index 00000000..495242d1 --- /dev/null +++ b/.github/workflows/java-module-test.yml @@ -0,0 +1,249 @@ +name: Java 9+ Module Support + +on: + push: + branches: [ 'master', 'main', 'release/**' ] + pull_request: + branches: [ '*' ] + +jobs: + # Test Java 9+ module support (JPMS) + # Verifies that module-info.java is properly compiled and the resulting + # JAR can be used with jlink to create custom Java runtimes. + module-test: + strategy: + matrix: + os: [ 'ubuntu-latest' ] + jdk_version: [ '11', '17', '21' ] + runs-on: ${{ matrix.os }} + name: Module Test (JDK ${{ matrix.jdk_version }}) + + steps: + - uses: actions/checkout@v4 + + - name: Cache JUnit dependencies + uses: actions/cache@v4 + id: cache-junit + with: + path: junit + key: junit-jars-v1 + + - name: Download junit-4.13.2.jar + if: steps.cache-junit.outputs.cache-hit != 'true' + run: wget --directory-prefix=$GITHUB_WORKSPACE/junit https://repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2.jar + + - name: Download hamcrest-all-1.3.jar + if: steps.cache-junit.outputs.cache-hit != 'true' + run: wget --directory-prefix=$GITHUB_WORKSPACE/junit https://repo1.maven.org/maven2/org/hamcrest/hamcrest-all/1.3/hamcrest-all-1.3.jar + + - name: Build native wolfSSL + uses: wolfSSL/actions-build-autotools-project@v1 + with: + repository: wolfSSL/wolfssl + ref: master + path: wolfssl + configure: --enable-jni + check: false + install: true + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: ${{ matrix.jdk_version }} + + - name: Set JUNIT_HOME + run: | + echo "JUNIT_HOME=$GITHUB_WORKSPACE/junit" >> "$GITHUB_ENV" + + - name: Set LD_LIBRARY_PATH + run: | + echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GITHUB_WORKSPACE/build-dir/lib" >> "$GITHUB_ENV" + + - name: Copy makefile + run: cp makefile.linux makefile + + - name: Build JNI library + run: PREFIX=$GITHUB_WORKSPACE/build-dir make + + - name: Build JAR with module support (ant) + run: ant build-jce-release + + - name: Verify module-info.class exists in JAR + run: | + echo "Checking for module-info.class in wolfcrypt-jni.jar..." + if jar tf lib/wolfcrypt-jni.jar | grep -q "module-info.class"; then + echo "SUCCESS: module-info.class found in JAR" + else + echo "FAILURE: module-info.class not found in JAR" + echo "JAR contents:" + jar tf lib/wolfcrypt-jni.jar | head -20 + exit 1 + fi + + - name: Verify module descriptor with jar --describe-module + run: | + echo "Describing module in wolfcrypt-jni.jar..." + jar --describe-module --file=lib/wolfcrypt-jni.jar + echo "" + echo "Verifying module name is 'com.wolfssl.wolfcrypt'..." + MODULE_NAME=$(jar --describe-module --file=lib/wolfcrypt-jni.jar 2>&1 | head -1 | cut -d' ' -f1) + if [ "$MODULE_NAME" = "com.wolfssl.wolfcrypt" ]; then + echo "SUCCESS: Module name is correct: $MODULE_NAME" + else + echo "FAILURE: Expected module name 'com.wolfssl.wolfcrypt', got '$MODULE_NAME'" + exit 1 + fi + + - name: Verify module exports correct packages + run: | + echo "Verifying module exports..." + EXPORTS=$(jar --describe-module --file=lib/wolfcrypt-jni.jar 2>&1) + echo "$EXPORTS" + echo "" + if echo "$EXPORTS" | grep -q "exports com.wolfssl.wolfcrypt"; then + echo "SUCCESS: exports com.wolfssl.wolfcrypt" + else + echo "FAILURE: missing 'exports com.wolfssl.wolfcrypt'" + exit 1 + fi + if echo "$EXPORTS" | grep -q "exports com.wolfssl.provider.jce"; then + echo "SUCCESS: exports com.wolfssl.provider.jce" + else + echo "FAILURE: missing 'exports com.wolfssl.provider.jce'" + exit 1 + fi + + - name: Test jlink can create runtime with module + run: | + echo "Testing jlink integration..." + jlink \ + --module-path lib/wolfcrypt-jni.jar:$JAVA_HOME/jmods \ + --add-modules com.wolfssl.wolfcrypt \ + --output test-jlink-runtime \ + --no-header-files \ + --no-man-pages + echo "" + echo "SUCCESS: jlink created custom runtime" + echo "Runtime modules:" + ./test-jlink-runtime/bin/java --list-modules + echo "" + echo "Verifying com.wolfssl.wolfcrypt module is present..." + if ./test-jlink-runtime/bin/java --list-modules | grep -q "com.wolfssl.wolfcrypt"; then + echo "SUCCESS: com.wolfssl.wolfcrypt module found in custom runtime" + else + echo "FAILURE: com.wolfssl.wolfcrypt module not found in custom runtime" + exit 1 + fi + + - name: Clean up jlink test runtime + run: rm -rf test-jlink-runtime + + - name: Run standard tests to verify module doesn't break functionality + run: ant test + + - name: Clean ant build for Maven test + run: ant clean + + - name: Maven build and verify module-info in JAR + run: | + echo "Building with Maven..." + mvn package -DskipTests -q + echo "" + MAVEN_JAR=$(ls target/wolfcrypt-jni-*.jar) + echo "Maven JAR: $MAVEN_JAR" + echo "" + echo "Checking for module-info.class in Maven-built JAR..." + if jar tf "$MAVEN_JAR" | grep -q "module-info.class"; then + echo "SUCCESS: module-info.class found in Maven JAR" + else + echo "FAILURE: module-info.class not found in Maven JAR" + jar tf "$MAVEN_JAR" | head -20 + exit 1 + fi + echo "" + echo "Verifying Maven JAR module descriptor..." + jar --describe-module --file="$MAVEN_JAR" + + - name: Clean Maven build + run: mvn clean -q + + - name: Show logs on failure + if: failure() || cancelled() + run: | + cat build/reports/*.txt 2>/dev/null || echo "No test reports found" + + # Verify Java 8 builds do NOT include module-info.class + # This ensures the conditional compilation works correctly for Java 8 users + java8-no-module-test: + runs-on: ubuntu-latest + name: Java 8 No Module Test + + steps: + - uses: actions/checkout@v4 + + - name: Cache JUnit dependencies + uses: actions/cache@v4 + id: cache-junit + with: + path: junit + key: junit-jars-v1 + + - name: Download junit-4.13.2.jar + if: steps.cache-junit.outputs.cache-hit != 'true' + run: wget --directory-prefix=$GITHUB_WORKSPACE/junit https://repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2.jar + + - name: Download hamcrest-all-1.3.jar + if: steps.cache-junit.outputs.cache-hit != 'true' + run: wget --directory-prefix=$GITHUB_WORKSPACE/junit https://repo1.maven.org/maven2/org/hamcrest/hamcrest-all/1.3/hamcrest-all-1.3.jar + + - name: Build native wolfSSL + uses: wolfSSL/actions-build-autotools-project@v1 + with: + repository: wolfSSL/wolfssl + ref: master + path: wolfssl + configure: --enable-jni + check: false + install: true + + - name: Setup Java 8 + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '8' + + - name: Set JUNIT_HOME + run: | + echo "JUNIT_HOME=$GITHUB_WORKSPACE/junit" >> "$GITHUB_ENV" + + - name: Set LD_LIBRARY_PATH + run: | + echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GITHUB_WORKSPACE/build-dir/lib" >> "$GITHUB_ENV" + + - name: Copy makefile + run: cp makefile.linux makefile + + - name: Build JNI library + run: PREFIX=$GITHUB_WORKSPACE/build-dir make + + - name: Build JAR with Java 8 (ant) + run: ant build-jce-release + + - name: Verify module-info.class is NOT in JAR (Java 8) + run: | + echo "Checking that module-info.class is NOT in wolfcrypt-jni.jar (Java 8 build)..." + if jar tf lib/wolfcrypt-jni.jar | grep -q "module-info.class"; then + echo "FAILURE: module-info.class should NOT be in Java 8 JAR" + exit 1 + else + echo "SUCCESS: module-info.class correctly absent from Java 8 JAR" + fi + + - name: Run standard tests to verify Java 8 compatibility + run: ant test + + - name: Show logs on failure + if: failure() || cancelled() + run: | + cat build/reports/*.txt 2>/dev/null || echo "No test reports found" diff --git a/README.md b/README.md index a1275c36..5e4ee7c2 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,51 @@ on the current release): ``` +### Java 9+ Module Support (JPMS) +--------- + +wolfCrypt JNI/JCE supports the Java Platform Module System (JPMS) introduced +in Java 9. This enables use with `jlink` for creating custom, minimal Java +runtimes. + +**Module Information:** + +- Module name: `com.wolfssl.wolfcrypt` +- Exported packages: `com.wolfssl.wolfcrypt`, `com.wolfssl.provider.jce` +- Service provider: `java.security.Provider` (WolfCryptProvider) + +**Conditional Compilation:** + +The `module-info.java` is conditionally compiled based on the JDK version used +to build: + +| JDK Used to Build | Resulting JAR | +|-------------------|---------------| +| Java 8 | Standard JAR (no module-info.class) | +| Java 9+ | Modular JAR (includes module-info.class) | + +When building with Java 8, the `module-info.java` is automatically excluded +from compilation, and the resulting JAR works as a standard classpath JAR. + +**Using with jlink:** + +When built with Java 9+, the wolfCrypt JNI/JCE JAR can be used with `jlink` to +create a custom Java runtime that includes the wolfCrypt module: + +``` +$ jlink \ + --module-path lib/wolfcrypt-jni.jar:$JAVA_HOME/jmods \ + --add-modules com.wolfssl.wolfcrypt \ + --output custom-runtime \ + --no-header-files \ + --no-man-pages + +$ ./custom-runtime/bin/java --list-modules +``` + +**Note:** The native wolfCrypt JNI shared library (`libwolfcryptjni.so/dylib`) +must still be available on the native library path at runtime. + ### Example / Test Code --------- diff --git a/build.xml b/build.xml index fa2d0571..5977cf8f 100644 --- a/build.xml +++ b/build.xml @@ -23,6 +23,7 @@ + @@ -48,6 +49,18 @@ + + + + + + + + + + + + @@ -161,8 +174,24 @@ + + + + + + + + - + + - + + + + + + java9-module + + [9,) + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + + compile-module-info + compile + + compile + + + 9 + + ${project.basedir}/src/java9 + + false + + + + + + + + diff --git a/src/java9/module-info.java b/src/java9/module-info.java new file mode 100644 index 00000000..abccafbe --- /dev/null +++ b/src/java9/module-info.java @@ -0,0 +1,50 @@ +/* module-info.java + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/** + * wolfCrypt JNI/JCE Module + * + * This module provides: + * - JNI bindings to the native wolfCrypt cryptography library + * (com.wolfssl.wolfcrypt) + * - A JCE provider implementation (com.wolfssl.provider.jce) + * + * Note: This module-info.java is only compiled when building with Java 9+. + * When building with Java 8, this file is excluded and the resulting JAR + * will be a standard (non-modular) JAR that works on the classpath. + */ +module com.wolfssl.wolfcrypt { + /* Required modules */ + requires java.logging; + + /* Export public API packages */ + exports com.wolfssl.wolfcrypt; + exports com.wolfssl.provider.jce; + + /* Declare service usage for ServiceLoader.load(Provider.class). + * Required for WolfCryptServiceLoaderTest which verifies the provider + * can be discovered via ServiceLoader. */ + uses java.security.Provider; + + /* Register wolfJCE as a security provider */ + provides java.security.Provider + with com.wolfssl.provider.jce.WolfCryptProvider; +}