From 47715fe30c234c4d0534db29872a98a7448e6a10 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 22 May 2026 11:31:42 +0300 Subject: [PATCH 01/11] Fixed cdoc-tool index usage --- cdoc/cdoc-tool.cpp | 72 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 17 deletions(-) diff --git a/cdoc/cdoc-tool.cpp b/cdoc/cdoc-tool.cpp index 52fe681f..c40d588a 100644 --- a/cdoc/cdoc-tool.cpp +++ b/cdoc/cdoc-tool.cpp @@ -42,20 +42,28 @@ static std::map str2level = { enum { RESULT_OK = 0, - RESULT_ERROR, - RESULT_USAGE + RESULT_ERROR = -1, + RESULT_USAGE = -2 }; -static void print_usage(ostream& ofs) +static void +print_version(ostream& ofs) { ofs << "cdoc-tool version: " << VERSION_STR << endl; ofs << "libcdoc version: " << libcdoc::getVersion() << endl; + ofs.flush(); +} + +static void +print_usage(ostream& ofs) +{ + ofs << "Usage:" << endl; ofs << "cdoc-tool encrypt --rcpt RECIPIENT [--rcpt...] [-v1] [--genlabel] --out OUTPUTFILE FILE [FILE...]" << endl; ofs << " Encrypt files for one or more recipients" << endl; ofs << " RECIPIENT has to be one of the following:" << endl; ofs << " [label]:cert:CERTIFICATE_FILE - public key from certificate file (DER format)" << endl; - ofs << " [label]:pkey:SECRET_KEY_HEX - hex encoded public key (DER format; rsa, secp384r1 or secp256r1 key)." << endl; - ofs << " [label]:pfkey:PUB_KEY_FILE - public key from file (DER format; rsa, secp384r1 or secp256r1 key)." << endl; + ofs << " [label]:pkey:SECRET_KEY_HEX - hex encoded public key (DER format; rsa, secp384r1, secp256r1 or secp521r1 key)." << endl; + ofs << " [label]:pfkey:PUB_KEY_FILE - public key from file (DER format; rsa, secp384r1, secp256r1 or secp521r1 key)." << endl; ofs << " [label]:skey:SECRET_KEY_HEX - AES key, hex encoded" << endl; ofs << " [label]:pw:PASSWORD - AES key derived from password with PWBKDF" << endl; ofs << " [label]:p11sk:SLOT:[PIN]:[PKCS11 ID]:[PKCS11 LABEL] - use AES key from PKCS11 module" << endl; @@ -68,7 +76,7 @@ static void print_usage(ostream& ofs) ofs << " Decrypt CDoc container using lock specified by label or number" << endl; ofs << " Supported arguments" << endl; ofs << " --label LABEL - lock label" << endl; - ofs << " --label_idx INDEX - lock number (1-based)" << endl; + ofs << " --lock-idx INDEX - lock number (1-based)" << endl; ofs << " --pkey PRIVATE_KEY_HEX - hex encoded private key (DER format)" << endl; ofs << " --pfkey PRIVATE_KEY_HEX - private key from file (DER format)" << endl; ofs << " --slot SLOT - PKCS11 slot number" << endl; @@ -366,7 +374,13 @@ static int ParseAndEncrypt(int argc, char *argv[]) } CDocCipher cipher; - return cipher.Encrypt(conf, rcpts); + if (int ret = cipher.Encrypt(conf, rcpts); ret != 0) { + cerr << "Encryption failed"; + return ret; + } else { + cout << "Container " << conf.out << " encrypted successfully" << endl; + } + return 0; } struct LockData { @@ -378,7 +392,11 @@ struct LockData { vector secret; int validate(ToolConf& conf) { - if (lock_label.empty() && (lock_idx == -1) && (slot < 0)) { + if (lock_idx == 0) { + LOG_ERROR("Lock indices start from 1"); + return RESULT_USAGE; + } + if (lock_label.empty() && (lock_idx < 0) && (slot < 0)) { LOG_ERROR("No label nor index was provided"); return RESULT_USAGE; } @@ -394,13 +412,13 @@ static int parse_key_data(LockData& ldata, const int& arg_idx, int argc, char *argv[]) { string_view arg(argv[arg_idx]); - if ((arg == "--label" || arg == "--label_idx") && (arg_idx + 1) < argc) { + if ((arg == "--label" || arg == "--label_idx" || arg == "--lock-idx") && (arg_idx + 1) < argc) { // Make sure the label or label index is provided only once. - if (!ldata.lock_label.empty() || ldata.lock_idx != -1) { + if (!ldata.lock_label.empty() || ldata.lock_idx > 0) { LOG_ERROR("The label or label's index was already provided"); return RESULT_USAGE; } - if (arg == "--label_idx") { + if (arg == "--label_idx" || arg == "--lock-idx") { size_t last_char_idx; string str(argv[arg_idx + 1]); ldata.lock_idx = std::stol(str, &last_char_idx); @@ -408,6 +426,10 @@ parse_key_data(LockData& ldata, const int& arg_idx, int argc, char *argv[]) LOG_ERROR("Label index is not a number"); return RESULT_USAGE; } + if (ldata.lock_idx < 1) { + LOG_ERROR("Lock indices start from 1"); + return RESULT_USAGE; + } } else { ldata.lock_label = argv[arg_idx + 1]; } @@ -546,7 +568,13 @@ static int ParseAndDecrypt(int argc, char *argv[]) CDocCipher cipher; RcptInfo rcpt {.type=RcptInfo::LOCK, .label=ldata.lock_label, .secret=ldata.secret, .p11={ldata.slot, ldata.key_id, ldata.key_label}, .lock_idx=ldata.lock_idx - 1}; - return cipher.Decrypt(conf, rcpt); + if (int ret = cipher.Decrypt(conf, rcpt); ret != 0) { + cerr << "Decryption failed" << endl; + return ret; + } else { + cout << "Container " << conf.input_files[0] << " decrypted successfully" << endl; + } + return 0; } static int ParseAndReEncrypt(int argc, char *argv[]) @@ -628,12 +656,20 @@ static int ParseAndReEncrypt(int argc, char *argv[]) } } + if ((ldata.lock_idx < 0) && (ldata.lock_label.empty())) { + LOG_ERROR("Lock index or label must be provided"); + return RESULT_USAGE; + } + CDocCipher cipher; - RcptInfo rcpt {.type=RcptInfo::LOCK, .label=ldata.lock_label, .secret=ldata.secret, .p11={ldata.slot, ldata.key_id, ldata.key_label}, .lock_idx=ldata.lock_idx}; - if (ldata.lock_idx != -1) { - return cipher.ReEncrypt(conf, rcpt, rcpts); + RcptInfo rcpt {.type=RcptInfo::LOCK, .label=ldata.lock_label, .secret=ldata.secret, .p11={ldata.slot, ldata.key_id, ldata.key_label}, .lock_idx=ldata.lock_idx - 1}; + if (int ret = cipher.ReEncrypt(conf, rcpt, rcpts); ret != 0) { + cerr << "Re-encryption failed" << std::endl; + return ret; + } else { + cout << "Successfully re-encrypted container " << conf.input_files[0] << " to " << conf.out << std::endl; } - return true; + return 0; } // @@ -671,6 +707,8 @@ static int ParseAndGetLocks(int argc, char *argv[]) int main(int argc, char *argv[]) { + print_version(cout); + if (argc < 2) { print_usage(cerr); return 1; @@ -694,7 +732,7 @@ int main(int argc, char *argv[]) cerr << "Invalid command: " << command << endl; } - if (retVal == 2) { + if (retVal == RESULT_USAGE) { // We print usage information only in case the parse-function returned 2. Value 1 indicates other error. print_usage(cout); } From fef2389ca52bdaf43dfdb994a0aa48e2ea431c4f Mon Sep 17 00:00:00 2001 From: lauris71 Date: Mon, 25 May 2026 16:38:37 +0300 Subject: [PATCH 02/11] Update cdoc/cdoc-tool.cpp Co-authored-by: Raul Metsma --- cdoc/cdoc-tool.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cdoc/cdoc-tool.cpp b/cdoc/cdoc-tool.cpp index c40d588a..c060d6e6 100644 --- a/cdoc/cdoc-tool.cpp +++ b/cdoc/cdoc-tool.cpp @@ -51,7 +51,6 @@ print_version(ostream& ofs) { ofs << "cdoc-tool version: " << VERSION_STR << endl; ofs << "libcdoc version: " << libcdoc::getVersion() << endl; - ofs.flush(); } static void From 884854288da0e9b6f9aa7633eadcc0d3f4397a81 Mon Sep 17 00:00:00 2001 From: lauris71 Date: Mon, 25 May 2026 16:38:44 +0300 Subject: [PATCH 03/11] Update cdoc/cdoc-tool.cpp Co-authored-by: Raul Metsma --- cdoc/cdoc-tool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cdoc/cdoc-tool.cpp b/cdoc/cdoc-tool.cpp index c060d6e6..21b9e33d 100644 --- a/cdoc/cdoc-tool.cpp +++ b/cdoc/cdoc-tool.cpp @@ -668,7 +668,7 @@ static int ParseAndReEncrypt(int argc, char *argv[]) } else { cout << "Successfully re-encrypted container " << conf.input_files[0] << " to " << conf.out << std::endl; } - return 0; + return RESULT_OK; } // From 57694470727384a42b77c8830baf4c350b3b0b69 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 15 Jul 2026 16:05:24 +0300 Subject: [PATCH 04/11] Fixed label UTF-8 escaping if locale is not C --- cdoc/Utils.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cdoc/Utils.cpp b/cdoc/Utils.cpp index b422d596..9495e2d0 100644 --- a/cdoc/Utils.cpp +++ b/cdoc/Utils.cpp @@ -26,6 +26,7 @@ #include #include +#include namespace libcdoc { @@ -145,6 +146,7 @@ buildURL(const std::string& host, int port) std::ostream& operator<<(std::ostream& escaped, urlEncode src) { + static const std::locale locC("C"); restoreFlags rf(escaped); escaped.fill('0'); escaped << std::hex; @@ -155,7 +157,7 @@ operator<<(std::ostream& escaped, urlEncode src) continue; } // Keep alphanumeric and other accepted characters intact - if (isalnum(uint8_t(c)) || c == '-' || c == '_' || c == '.' || c == '~') { + if (std::isalnum(c, locC) || c == '-' || c == '_' || c == '.' || c == '~') { escaped << c; continue; } From 12253559e0c1c2cd49e69f43e13167371ac71ee9 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 15 Jul 2026 16:07:37 +0300 Subject: [PATCH 05/11] Fixed label generation for non-C locales --- cdoc/Utils.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cdoc/Utils.cpp b/cdoc/Utils.cpp index b422d596..9495e2d0 100644 --- a/cdoc/Utils.cpp +++ b/cdoc/Utils.cpp @@ -26,6 +26,7 @@ #include #include +#include namespace libcdoc { @@ -145,6 +146,7 @@ buildURL(const std::string& host, int port) std::ostream& operator<<(std::ostream& escaped, urlEncode src) { + static const std::locale locC("C"); restoreFlags rf(escaped); escaped.fill('0'); escaped << std::hex; @@ -155,7 +157,7 @@ operator<<(std::ostream& escaped, urlEncode src) continue; } // Keep alphanumeric and other accepted characters intact - if (isalnum(uint8_t(c)) || c == '-' || c == '_' || c == '.' || c == '~') { + if (std::isalnum(c, locC) || c == '-' || c == '_' || c == '.' || c == '~') { escaped << c; continue; } From c6367cd4e99e85b2db820d01206fba0567559ffc Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 30 Sep 2026 17:16:01 +0300 Subject: [PATCH 06/11] Fixed Java utf8 handling, added Java tests --- CMakeLists.txt | 5 + examples/CMakeLists.txt | 67 + examples/java/README.md | 57 +- examples/java/build.gradle | 197 ++- .../src/main/java/ee/ria/cdoc/CDocTool.java | 1180 +++++++++++------ .../test/java/ee/ria/cdoc/CDocCryptoTest.java | 396 ++++++ .../src/test/java/ee/ria/cdoc/CDocTest.java | 229 ++++ libcdoc.i | 133 +- std_map.i | 89 ++ std_map_string_view.i | 102 ++ std_string_utf8.i | 164 +++ std_string_view.i | 133 +- 12 files changed, 2210 insertions(+), 542 deletions(-) create mode 100644 examples/CMakeLists.txt create mode 100644 examples/java/src/test/java/ee/ria/cdoc/CDocCryptoTest.java create mode 100644 examples/java/src/test/java/ee/ria/cdoc/CDocTest.java create mode 100644 std_map.i create mode 100644 std_map_string_view.i create mode 100644 std_string_utf8.i diff --git a/CMakeLists.txt b/CMakeLists.txt index e11858a3..769ad573 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,6 +69,11 @@ if(LIBCDOC_WITH_DOCS) endif() add_subdirectory(cdoc) +# Binary dir of the cdoc sub-build; used by examples/CMakeLists.txt to locate +# the SWIG-generated Java bindings. Correct also when libcdoc is included as +# a subproject of a larger build. +set(CDOC_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/cdoc) +add_subdirectory(examples) find_package(Boost COMPONENTS unit_test_framework QUIET) if (TARGET Boost::unit_test_framework) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 00000000..c2943dc1 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,67 @@ +# Java example (CDocTool) built with the Gradle wrapper in examples/java. +# +# The example compiles against the SWIG-generated Java bindings produced by +# the cdoc_java target. It is added to the default build only when: +# * SWIG was found and the cdoc_java target exists, and +# * a Java Development Kit was found (Java_Development_FOUND), and +# * the Gradle wrapper script is present in the source tree, and +# * LIBCDOC_BUILD_JAVA_EXAMPLE is ON (default). +# +# The Gradle invocation is given absolute paths to the SWIG Java sources and +# the JNI library of the current build, so no manual configuration is needed. + +set(JAVA_EXAMPLE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/java) + +if(WIN32) + set(GRADLE_WRAPPER ${JAVA_EXAMPLE_DIR}/gradlew.bat) +else() + set(GRADLE_WRAPPER ${JAVA_EXAMPLE_DIR}/gradlew) +endif() + +if(NOT EXISTS ${GRADLE_WRAPPER}) + message(STATUS "Gradle wrapper not found in ${JAVA_EXAMPLE_DIR} - skipping Java example") + return() +endif() + +if(NOT TARGET cdoc_java) + message(STATUS "cdoc_java target not available - skipping Java example") + return() +endif() + +if(NOT Java_Development_FOUND) + message(STATUS "Java Development Kit not found - skipping Java example") + return() +endif() + +option(LIBCDOC_BUILD_JAVA_EXAMPLE "Build the Java CDocTool example with Gradle" ON) +if(NOT LIBCDOC_BUILD_JAVA_EXAMPLE) + return() +endif() + +# Directory containing the SWIG-generated .java files (OUTPUT_DIR of +# swig_add_library in cdoc/CMakeLists.txt). CDOC_BINARY_DIR is set by the +# parent CMakeLists.txt right after add_subdirectory(cdoc); fall back to the +# conventional location if this file is used standalone. +if(NOT CDOC_BINARY_DIR) + set(CDOC_BINARY_DIR ${CMAKE_BINARY_DIR}/cdoc) +endif() +set(JAVA_EXAMPLE_SWIG_DIR ${CDOC_BINARY_DIR}/java) +set(JAVA_EXAMPLE_JAR ${JAVA_EXAMPLE_DIR}/build/libs/CDocTool.jar) + +add_custom_command( + OUTPUT ${JAVA_EXAMPLE_JAR} + COMMENT "Building and testing Java example (CDocTool.jar) with Gradle" + COMMAND ${GRADLE_WRAPPER} + --no-daemon + --quiet + -PswigJavaDir=${JAVA_EXAMPLE_SWIG_DIR} + -PjniLibDir=$ + build + WORKING_DIRECTORY ${JAVA_EXAMPLE_DIR} + DEPENDS cdoc_java + VERBATIM +) + +add_custom_target(cdoc_java_example ALL + DEPENDS ${JAVA_EXAMPLE_JAR} +) diff --git a/examples/java/README.md b/examples/java/README.md index ee8c206a..8368ad14 100644 --- a/examples/java/README.md +++ b/examples/java/README.md @@ -1,9 +1,62 @@ # Build instructions for Java -## Build +The Java example (`CDocTool`) compiles against the SWIG-generated Java +bindings produced by the libcdoc CMake build. + +## Automatic build (recommended) + +When the libcdoc project is configured with SWIG and a JDK available, the +example is built automatically as part of the default CMake build: + + cmake --preset macos + cmake --build build/macos + +This produces `examples/java/build/libs/CDocTool.jar`. Set +`-DLIBCDOC_BUILD_JAVA_EXAMPLE=OFF` to disable it. + +## Manual build + +First build the main CMake project so that the SWIG Java bindings and the +JNI library exist, then run the Gradle wrapper: ./gradlew jar +`build.gradle` locates the SWIG-generated sources automatically by scanning +the well-known CMake build directories (`build//cdoc/java`). If your +build tree lives elsewhere, point the build at it explicitly: + + ./gradlew jar -PswigJavaDir=/path/to/build/cdoc/java -PjniLibDir=/path/to/build/cdoc + +or with environment variables: + + LIBCDOC_SWIG_JAVA_DIR=/path/to/build/cdoc/java LIBCDOC_JNI_LIB_DIR=/path/to/build/cdoc ./gradlew jar + +## Test + +The example has JUnit 5 tests (in `src/test/java`) that run against the +compiled JNI library: + + ./gradlew test + +The CMake-driven build runs the tests automatically (it invokes +`gradlew build`, which includes the `test` task). + ## Run - java -Djava.library.path=/some/path/lib -jar build/libs/CDocTool.jar \ No newline at end of file + ./gradlew run + +or directly: + + java -jar build/libs/CDocTool.jar + +The JNI library built by the CMake project is located automatically. The +resolution order is: + +1. `--library ` command line argument +2. `-Dcdoc.library=` system property +3. `ee/ria/cdoc/jni.properties` baked into the jar by Gradle (records the + JNI library directory at build time) +4. Well-known CMake build directories (`build//cdoc`) relative to + the working directory +5. `java.library.path` (set by `./gradlew run`, or pass + `-Djava.library.path=/path/to/build/cdoc` to `java`) diff --git a/examples/java/build.gradle b/examples/java/build.gradle index 4acef996..2bad8da3 100755 --- a/examples/java/build.gradle +++ b/examples/java/build.gradle @@ -1,17 +1,212 @@ plugins { id 'java' } + group 'ee.ria' -sourceSets.main.java.srcDirs += ['../../build/macos/cdoc/java', '../../../../build/client/libcdoc/cdoc/java'] + +/* + * Robust build for the CDocTool Java example. + * + * The SWIG-generated Java bindings are produced by the CMake build of the + * parent project and are not checked into the repository. This build script + * locates them using, in order of precedence: + * + * 1. -PswigJavaDir= (Gradle project property) + * 2. LIBCDOC_SWIG_JAVA_DIR env (environment variable) + * 3. Auto-detection (scans well-known CMake build directories) + * + * The native JNI library directory (used by the 'run' task) can be set with: + * + * 1. -PjniLibDir= (Gradle project property) + * 2. LIBCDOC_JNI_LIB_DIR env (environment variable) + * 3. Auto-detection (same scan as above) + */ + +def projectDir = layout.projectDirectory.asFile + +static File findSwigJavaDir(File root) { + // Scan well-known CMake build directory layouts for the SWIG output. + // Paths are relative to examples/java, i.e. two levels below the + // libcdoc project root. + def candidates = [ + '../../build/macos/cdoc/java', + '../../build/macos-debug/cdoc/java', + '../../build/ninja/cdoc/java', + '../../build/linux/cdoc/java', + '../../build/cdoc/java', + '../../../build/cdoc/java', + '../../../build/client/libcdoc/cdoc/java', + ] + for (String rel : candidates) { + File dir = new File(root, rel) + if (new File(dir, 'CDoc.java').isFile()) { + return dir.canonicalFile + } + } + // Fall back to glob over /build/*/cdoc/java + def buildRoot = new File(root, '../../build') + if (buildRoot.isDirectory()) { + def found = buildRoot.listFiles()?.findAll { b -> + new File(b, 'cdoc/java/CDoc.java').isFile() + } + if (found && !found.isEmpty()) { + return new File(found.sort().first(), 'cdoc/java').canonicalFile + } + } + return null +} + +static File findJniLibDir(File root) { + def names = ['libcdoc_java.jnilib', 'libcdoc_javad.jnilib', + 'libcdoc_java.so', 'libcdoc_javad.so', + 'cdoc_java.dll', 'cdoc_javad.dll'] + def candidates = [ + '../../build/macos/cdoc', + '../../build/macos-debug/cdoc', + '../../build/ninja/cdoc', + '../../build/linux/cdoc', + '../../build/cdoc', + '../../../build/cdoc', + ] + for (String rel : candidates) { + for (String name : names) { + if (new File(root, "$rel/$name").isFile()) { + return new File(root, rel).canonicalFile + } + } + } + def buildRoot = new File(root, '../../build') + if (buildRoot.isDirectory()) { + def found = buildRoot.listFiles()?.findAll { b -> + names.any { new File(b, "cdoc/$it").isFile() } + } + if (found && !found.isEmpty()) { + return new File(found.sort().first(), 'cdoc').canonicalFile + } + } + return null +} + +File swigJavaDir = findProperty('swigJavaDir')?.with { file(it) } + ?: System.getenv('LIBCDOC_SWIG_JAVA_DIR')?.with { file(it) } + ?: findSwigJavaDir(projectDir) + +File jniLibDir = findProperty('jniLibDir')?.with { file(it) } + ?: System.getenv('LIBCDOC_JNI_LIB_DIR')?.with { file(it) } + ?: findJniLibDir(projectDir) + +if (swigJavaDir == null) { + throw new GradleException(""" + |Could not locate SWIG-generated Java bindings. + | + |Build the libcdoc CMake project first (it must be configured with + |SWIG and Java/JNI available), then either: + | * pass -PswigJavaDir=/cdoc/java, or + | * set LIBCDOC_SWIG_JAVA_DIR=/cdoc/java + | + |Searched under: $projectDir + """.stripMargin()) +} + +if (!new File(swigJavaDir, 'CDoc.java').isFile()) { + throw new GradleException( + "swigJavaDir '$swigJavaDir' does not contain CDoc.java. " + + "Make sure the CMake build with SWIG/Java has been run.") +} + +logger.lifecycle("Using SWIG Java sources: $swigJavaDir") +if (jniLibDir != null) { + logger.lifecycle("Using JNI library directory: $jniLibDir") +} else { + logger.lifecycle("JNI library directory not found - 'run' task will need -PjniLibDir") +} + +// Generate ee/ria/cdoc/jni.properties with the build-time JNI library +// directory. CDocTool reads this resource at runtime and loads the project's +// compiled JNI library from there, so no --library flag is needed. +def jniResourcesDir = layout.buildDirectory.dir('generated/resources/jni') + +tasks.register('generateJniProperties') { + def propsFile = jniResourcesDir.map { it.file('ee/ria/cdoc/jni.properties') } + inputs.property('jniLibDir', jniLibDir?.absolutePath ?: '') + outputs.file(propsFile) + doLast { + File f = propsFile.get().asFile + f.parentFile.mkdirs() + Properties props = new Properties() + if (jniLibDir != null) { + props.setProperty('jniLibDir', jniLibDir.absolutePath) + } + f.withOutputStream { props.store(it, 'Generated by build.gradle') } + } +} + +sourceSets { + main { + java { + srcDirs = ['src/main/java', swigJavaDir.absolutePath] + } + output.dir(jniResourcesDir.get().asFile, builtBy: 'generateJniProperties') + } +} + java { targetCompatibility JavaVersion.VERSION_17 sourceCompatibility JavaVersion.VERSION_17 } + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} + base { archivesName = 'CDocTool' } + +repositories { + mavenCentral() +} + +dependencies { + testImplementation platform('org.junit:junit-bom:5.10.2') + testImplementation 'org.junit.jupiter:junit-jupiter' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +// Resolve the actual JNI library file (with debug postfix and platform +// suffix) for the benefit of tests, which load it via -Dcdoc.library. +def jniLibFile = jniLibDir == null ? null : ( + ['libcdoc_java.jnilib', 'libcdoc_javad.jnilib', + 'libcdoc_java.dylib', 'libcdoc_javad.dylib', + 'libcdoc_java.so', 'libcdoc_javad.so', + 'cdoc_java.dll', 'cdoc_javad.dll'] + .collect { new File(jniLibDir, it) } + .find { it.isFile() }) + jar { manifest { attributes 'Main-Class': 'ee.ria.cdoc.CDocTool' } } + +tasks.named('test', Test) { + useJUnitPlatform() + if (jniLibFile != null) { + systemProperty 'cdoc.library', jniLibFile.absolutePath + } else if (jniLibDir != null) { + systemProperty 'java.library.path', jniLibDir.absolutePath + } + testLogging { + events 'passed', 'failed', 'skipped' + } +} + +tasks.register('run', JavaExec) { + description = 'Runs CDocTool (requires the JNI library to be built by CMake)' + group = 'application' + mainClass = 'ee.ria.cdoc.CDocTool' + classpath = sourceSets.main.runtimeClasspath + if (jniLibDir != null) { + systemProperty 'java.library.path', jniLibDir.absolutePath + } +} diff --git a/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java b/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java index 2b0cf6be..5c04b15e 100644 --- a/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java +++ b/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java @@ -3,311 +3,497 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintStream; +import java.math.BigInteger; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.SecureRandom; +import java.security.interfaces.ECPrivateKey; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.MGF1ParameterSpec; +import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; -import java.util.HashMap; -import java.util.Collection; +import java.util.Arrays; import java.util.HexFormat; -import java.util.concurrent.locks.Lock; +import java.util.List; +import javax.crypto.KeyAgreement; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; public class CDocTool { private enum Action { INVALID, ENCRYPT, DECRYPT, - LOCKS, - TEST + LOCKS } - private static HexFormat hex = HexFormat.of(); + private static final HexFormat hex = HexFormat.of(); public static String getArg(int arg_idx, String[] args) { arg_idx += 1; if (arg_idx >= args.length) { - System.err.println("Invalid arguments"); - System.exit(1); + failUsage("Missing argument"); } return args[arg_idx]; } // Make logger static to ensure that it is not garbage-collected as long as it is attached to library private static Logger logger; - + + private static void printUsage(PrintStream ofs) { + ofs.print(""" + Usage: + CDocTool [--library JNI_LIBRARY] ACTION ARGUMENTS FILE(S) + + --library before ACTION is the path to the libcdoc JNI library. + Everywhere else --library refers to the PKCS#11 module. + + Actions: + encrypt Encrypt files + decrypt Decrypt files + locks List locks in a CDoc file + + Encryption arguments: + --rcpt RECIPIENT Recipient info, where recipient is one of the following: +