Repository: martinpaljak/ant-javacard Branch: next Commit: f97f8c9f44a8 Files: 57 Total size: 221.2 KB Directory structure: gitextract_mjb5d4jj/ ├── .github/ │ └── workflows/ │ └── robot.yml ├── .gitignore ├── .gitmodules ├── .mvn/ │ └── wrapper/ │ └── maven-wrapper.properties ├── LICENSE ├── LICENSES/ │ ├── Apache-2.0.txt │ └── MIT.txt ├── Makefile ├── README.md ├── REUSE.toml ├── build.xml ├── capfile/ │ ├── pom.xml │ ├── spotbugs.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ ├── module-info.java │ │ │ └── pro/ │ │ │ └── javacard/ │ │ │ ├── capfile/ │ │ │ │ ├── AID.java │ │ │ │ ├── CAPFile.java │ │ │ │ ├── CAPPackage.java │ │ │ │ ├── HexUtils.java │ │ │ │ └── WellKnownAID.java │ │ │ └── sdk/ │ │ │ ├── ExportFileHelper.java │ │ │ ├── JavaCardSDK.java │ │ │ ├── OffCardVerifier.java │ │ │ ├── SDKVersion.java │ │ │ └── VerifierError.java │ │ └── resources/ │ │ └── pro/ │ │ └── javacard/ │ │ └── capfile/ │ │ └── aid_list.properties │ └── test/ │ └── java/ │ └── pro/ │ └── javacard/ │ ├── capfile/ │ │ └── TestWellKnownAID.java │ └── sdk/ │ ├── TestExportFiles.java │ └── TestSDKs.java ├── fails.xml ├── kits.xml ├── mvnw ├── mvnw.cmd ├── pom.xml ├── spotbugs.xml ├── src/ │ └── testapplets/ │ ├── empty/ │ │ └── Empty.java │ ├── fail/ │ │ └── Fail.java │ ├── integer/ │ │ └── EmptyInt.java │ ├── library/ │ │ └── SomeLibrary.java │ ├── libraryuser/ │ │ └── LibraryUser.java │ ├── multiapp/ │ │ ├── First.java │ │ └── Second.java │ └── stringdefs/ │ └── Empty.java ├── task/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── pro/ │ └── javacard/ │ └── ant/ │ ├── DummyMain.java │ ├── HelpingBuildException.java │ ├── JCApplet.java │ ├── JCCap.java │ ├── JCImport.java │ ├── JCSources.java │ ├── JavaCard.java │ └── Misc.java ├── tests-1.8.xml ├── tests-11.xml ├── tests-17.xml ├── tests-21.xml ├── tests-fail.xml └── tests.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/robot.yml ================================================ on: push: tags: - 'v*' branches: - master - next pull_request: branches: - master permissions: contents: write name: Build robot jobs: build: runs-on: ubuntu-24.04 strategy: matrix: java: [ 8, 11, 17, 21 ] name: Java ${{ matrix.java }} steps: - name: Checkout with submodules uses: actions/checkout@v6.0.2 with: submodules: true fetch-depth: 0 # To make git describe give the intended output fetch-tags: true - name: Setup java uses: actions/setup-java@v5.2.0 with: java-version: ${{ matrix.java }} distribution: temurin cache: maven - name: Run Maven for capfile if: matrix.java != 8 run: ./mvnw -B --no-transfer-progress -T1C -U -Pcheck verify - name: Run ANT tests run: ant test dist - name: Get SHA-256 of ant-javacard.jar run: echo "JARSUM=$(shasum -a 256 --tag ant-javacard.jar)" >> "$GITHUB_ENV" - name: Deploy package or snapshot if: github.event_name != 'pull_request' && matrix.java == '11' uses: martinpaljak/maven-ssh-deploy@v1 with: key: ${{ secrets.SSH_KEY }} user: mvn@mvn.javacard.pro host_fp: SHA256:auiF3nHWvDmvq2stDl+QEECCqMcDp+FY1/bDRnvxpRw - name: Upload if: github.event_name != 'pull_request' && matrix.java == '11' uses: actions/upload-artifact@v7.0.1 with: name: ant-javacard.jar path: ant-javacard.jar - name: Release if: startsWith(github.ref, 'refs/tags/v') && matrix.java == '11' id: create_release uses: softprops/action-gh-release@v3.0.0 with: files: | ant-javacard.jar fail_on_unmatched_files: true body: "Release ${{ github.ref_name }}, `${{ env.JARSUM }}` (built with JDK-11)" prerelease: true # manually promoted ================================================ FILE: .gitignore ================================================ **/target *.cap *.class *.iml *~ /*.iml /*.jar /*.jca /.idea /.mvn/wrapper/maven-wrapper.jar /target /build /testlib hs_err_pid* pom.xml.versionsBackup ================================================ FILE: .gitmodules ================================================ [submodule "sdks"] path = sdks url = ../oracle_javacard_sdks.git ================================================ FILE: .mvn/wrapper/maven-wrapper.properties ================================================ wrapperVersion=3.3.4 distributionType=script distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.15/apache-maven-3.9.15-bin.zip wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015-2024 Martin Paljak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: LICENSES/Apache-2.0.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed 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. ================================================ FILE: LICENSES/MIT.txt ================================================ MIT License Copyright (c) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ TZ = UTC # same as Github export TZ SHELL := /bin/bash JDK := zulu JAVA8 := /Library/Java/JavaVirtualMachines/$(JDK)-8.jdk/Contents/Home JAVA11 := /Library/Java/JavaVirtualMachines/$(JDK)-11.jdk/Contents/Home JAVA17 := /Library/Java/JavaVirtualMachines/$(JDK)-17.jdk/Contents/Home JAVA21 := /Library/Java/JavaVirtualMachines/$(JDK)-21.jdk/Contents/Home default: today reportjava ./mvnw package ant test dist: reportjava JAVA_HOME=$(JAVA11) ant clean dist shasum -a 256 --tag ant-javacard.jar reportjava: @echo using java $(shell java -version 2>&1 | grep version) from \"$(JAVA_HOME)\" jar: JAVA_HOME=$(JAVA8) ant clean dist cap: # run maven with JDK21 JAVA_HOME=$(JAVA21) ./mvnw package full: ./mvnw package -Pfixup,check 8: JAVA_HOME=$(JAVA8) ant test 11: JAVA_HOME=$(JAVA11) ant test 17: JAVA_HOME=$(JAVA17) ant test 21: JAVA_HOME=$(JAVA21) ant test all: cap 8 11 17 21 clean: rm -f *~ *.cap reuse: reuse --no-multiprocessing lint today: # for a dirty tree, set the date to today test -z "$(shell git status --porcelain)" || ./mvnw versions:set -DnewVersion=$(shell date +%y.%m.%d)-SNAPSHOT -DgenerateBackupPoms=false ================================================ FILE: README.md ================================================ # Building JavaCard applet CAP files with Ant > Easy to use [Apache Ant](https://ant.apache.org/) task for building JavaCard CAP files in a declarative way. Have a consistent and concise build declaration for JavaCard applets, no matter which JavaCard SDK version you use or target. [![Latest release](https://img.shields.io/github/release/martinpaljak/ant-javacard.svg)](https://github.com/martinpaljak/ant-javacard/releases/latest)  [![Latest version](https://img.shields.io/maven-metadata/v?label=latest&metadataUrl=https%3A%2F%2Fmvn.javacard.pro%2Fmaven%2FSNAPSHOTS%2Fcom%2Fgithub%2Fmartinpaljak%2Fant-javacard%2Fmaven-metadata.xml)](https://gist.github.com/martinpaljak/c77d11d671260e24eef6c39123345cae)  [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/martinpaljak/ant-javacard/blob/master/LICENSE)  [![Build status](https://github.com/martinpaljak/ant-javacard/actions/workflows/robot.yml/badge.svg?branch=master)](https://github.com/martinpaljak/ant-javacard/actions)  [![Made in Estonia](https://img.shields.io/badge/Made_in-Estonia-0072CE)](https://estonia.ee) ## Features * **[Do What I Mean](http://en.wikipedia.org/wiki/DWIM)**. You will [love it](#happy-users)! * **No dependencies**, no extra or unrelated downloads. Just **a ~50KB reproducible jar file**. * Supports **all available JavaCard SDK versions**: 2.1.2, 2.2.1, 2.2.2, 3.0.3, 3.0.4, 3.0.5, 3.1.0 and 3.2.0 * Get one from [oracle.com](https://www.oracle.com/java/technologies/javacard-sdk-downloads.html) or use the [handy Github repository](https://github.com/martinpaljak/oracle_javacard_sdks) * **Works on all platforms** with LTS Java 1.8+: Windows, OSX, Linux. * **[Usable SDK-s depend on JDK version](https://github.com/martinpaljak/ant-javacard/wiki/JavaCard-SDK-and-JDK-version-compatibility)** * Almost **everything integrates** or works with Ant. * Trigger it [from Maven](https://github.com/martinpaljak/ant-javacard/wiki/How-to-use-from-Maven) or via [Gradle wrapper](https://github.com/bertrandmartel/javacard-gradle-plugin) * Can be easily integrated into **continuous integration** workflows. * Generates CAP files from **sources** or **pre-compiled** class files. * Import **external libraries**: natural use of `.jar` libraries and/or `.exp` files. * **No restrictions** on project folder layout (but `src/main/javacard` works). > [!TIP] > Loading JavaCard applets is equally pleasing with **[GlobalPlatformPro](https://github.com/martinpaljak/GlobalPlatformPro)** ## Download & Use * Download [`ant-javacard.jar`](https://github.com/martinpaljak/ant-javacard/releases/latest/download/ant-javacard.jar) * Or use the download task: ```xml ``` * Then load the task with the following in your `build.xml` file: ```xml ``` * Now you can create applets within your Ant targets like this: ```xml ``` (which results in output similar to this) ``` target: [cap] INFO: using JavaCard 3.0.5 SDK in sdks/jc305u4_kit [cap] INFO: targeting JavaCard 2.2.2 SDK in sdks/jc222_kit [cap] Setting package name to testapplets.empty [cap] INFO: generated applet AID: A000000617008E5CDAAE01 for testapplets.empty.Empty [cap] Building CAP with 1 applet from package testapplets.empty (AID: A000000617008E5CDAAE) [cap] testapplets.empty.Empty A000000617008E5CDAAE01 [compile] Compiling files from /Users/martin/projects/ant-javacard/src/testapplets/empty [compile] Compiling 1 source file to /var/folders/gf/_m9mq9td3lz32qv1hd4r12yw0000gn/T/jccpro841338375581620546 [cap] CAP saved to /Users/martin/projects/ant-javacard/Empty_A000000617008E5CDAAE_50da91a4_2.2.2.cap ``` ## Recommended setup Based on the [JavaCard SDK and JDK version compatibility matrix](https://github.com/martinpaljak/ant-javacard/wiki/JavaCard-SDK-and-JDK-version-compatibility). - Targeting JC 3.0.4 or later (modern JavaCard-s) - Use JDK 17 (don't forget to set `$JAVA_HOME`) - Use JavaCard SDK 3.2.0 (`jckit="sdks/jc320v24.0_kit"`) with right target (`targetsdk="3.x.y"`) - NOTE: depending on your external components, absence of v2.3 export files will force you to stick with JavaCard SDK 3.1 - Targeting JC 2.2.x or 3.0.1 (older JavaCard-s) - Use JDK 11 (don't forget to set `$JAVA_HOME`) - Use JavaCard SDK 3.0.5u4 (`jckit="sdks/jc305u4_kit"`) with right target (`targetsdk="sdks/jc222_kit"`) > [!NOTE] > ant-javacard will continue to support using legacy JavaCard 2.X SDK-s (and thus JDK-8) for as long as this is achievable with sane effort ## Syntax Sample: ```xml ``` Details: * `javacard` tag - generic task * `jckit` attribute - path to the JavaCard SDK that is used if individual `cap` does not specify one. Optional if `cap` defines one, required otherwise. * `cap` tag - construct a CAP file * `jckit` attribute - path to the JavaCard SDK to be used. Optional if `javacard` defines one, required otherwise. * `targetsdk` attribute - path to the target JavaCard SDK (or `"3.0.X"` target version when using JavaCard SDK v3.1), to be used for this CAP. Optional, value of `jckit` used by default. Allows to use a more recent converter to target older JavaCard platforms. * `sources` attribute - path(s) to Java source code, to be compiled against the JavaCard SDK. Either `sources` or `classes` is required, unless `src/main/javacard` or `src/main/java` exists. * `sources2` attribute - additional sources to build per-platform applets. Optional, deprecated (use multiple paths for `sources`) * `classes` attribute - path to pre-compiled class files to be assembled into a CAP file. If both `classes` and `sources` are specified, compiled class files will be put to `classes` folder, which is created if missing. * `includes` attribute - comma or space separated list of patterns of files that must be included (like `**/SomeFile.java`). * `excludes` attribute - comma or space separated list of patterns of files that must be excluded. * `package` attribute - name of the package of the CAP file. Optional for applets - set to the parent package of the applet class if left unspecified, required for libraries * `version` attribute - version of the package. Optional - defaults to 0.0 if left unspecified. * `aid` attribute - AID (hex) of the package. Recommended - or set to the 5 first bytes of the applet AID if left unspecified. * `output` attribute - path where to save the generated CAP file. Optional, see below for variables. * `export` attribute - path (folder) where to place the JAR and generated EXP file. Optional. * `exportmap` attribute - if set to true, use pre-defined export file. Optional. * `jar` attribute - path where to save the generated archive JAR file. Optional. * `jca` attribute - path where to save the generated JavaCard Assembly (JCA) file. Optional. * `verify` attribute - if set to false, disables verification of the resulting CAP file with offcardeverifier. Optional. * `debug` attribute - if set to true, generates debug CAP components. Optional. * `strip` attribute - if set to true, removes class files from target CAP. Optional. * `ints` attribute - if set to true, enables support for 32 bit `int` type. Optional. * `applet` tag - for creating an applet inside the CAP * `class` attribute - class of the Applet where install() method is defined. Required. * `aid` attribute - AID (hex) of the applet. Recommended - or set to package `aid`+`i` where `i` is index of the applet definition in the build.xml instruction * `import` tag - for linking against external components/libraries, like `GPSystem` or `OPSystem` * `exps` attribute - path to the folder keeping `.exp` files. Optional. Required if file in `jar` does not include .exp files. * `jar` attribute - path to the JAR file for compilation. Required if using `sources` mode and not necessary with `classes` mode if java code is already compiled Notes: * `jc.home` property has the highest precedence, followed by `jckit` path of `cap`, followed by path in `javacard`, followed by `JC_HOME` environment variable. SDK must be valid to be considered for use. * All source files are expected to be UTF-8. It is a sane choice, please use it. ### Output file name variables The default file name template is `%n_%a_%h_%j_%J.cap` which results in a file name similar to `SomeApplet_010203040506_9a037e30_2.2.2_jdk11.cap`. Following substitutions are available: * `%h` - 8 character prefix (hex) of the SHA-256 Load File Data Block hash of the CAP file * `%H` - SHA-256 Load File Data Block hash (hex) of the CAP file * `%n` - _common name_ of the entity, either applet class (if only one applet) or package name * `%p` - package name * `%a` - package AID (hex) * `%j` - targeted JavaCard version (ex: 3.0.5) * `%J` - used JDK version (ex: jdk11) * `%v` - applet package version (same as `version` attribute for `cap`, ex: v1.0) ### Command line utility `ant-javacard.jar` can be used to dump built .cap file metadata and to re-run off-card verifier. - dump .cap file metadata - `java -jar ant-javacard.jar ` - run off-card verifier - `java -jar ant-javacard.jar [] ` ### Environment variables - `JAVA_HOME` - path to the JDK to be used. - `JC_HOME` - path to the JavaCard SDK to be used if not specified in the build file. - `ANT_JAVACARD_TMP` - path to the temporary folder to be used for building CAP files. This is not cleaned after use. - `ANT_JAVACARD_DEBUG` - if set, shows debug output. ## Maven dependency Releases are published to [`https://mvn.javacard.pro/maven/`](https://mvn.javacard.pro/maven/). To use it, add this to your `pom.xml`: ```xml javacard-pro https://mvn.javacard.pro/maven/ ``` Pushes to Maven Central happen manually and only for selected final versions. ## License * [MIT](./LICENSE) ## Happy users A random list of users, with a public link: * Applets: * [IsoApplet](https://github.com/philipWendland/IsoApplet) by [@philipWendland](https://github.com/philipWendland) * [NdefApplet](https://github.com/promovicz/javacard-ndef) by [@promovicz](https://github.com/promovicz) * [GidsApplet](https://github.com/vletoux/GidsApplet) by [@vletoux](https://github.com/vletoux) * [LedgerWalletApplet](https://github.com/LedgerHQ/ledger-javacard) by [@LedgerHQ](https://github.com/LedgerHQ) * [KeePassNFC](https://github.com/nfd/smartcard_crypto_applet) by [@nfd](https://github.com/nfd) * [PivApplet](https://github.com/arekinath/PivApplet) (PIV) by [@arekinath](https://github.com/arekinath) * [OpenFIPS201](https://github.com/makinako/OpenFIPS201) (PIV) by [@makinako](https://github.com/makinako) * [Cryptonit](https://github.com/mbrossard/cryptonit-applet) (PIV) by [@mbrossard](https://github.com/mbrossard) * [HTOP NDEF](https://github.com/petrs/hotp_via_ndef) by [@petrs](https://github.com/petrs) * [Yubikey OTP](https://github.com/arekinath/YkOtpApplet) by [@arekinath](https://github.com/arekinath) * [SmartPGP](https://github.com/ANSSI-FR/SmartPGP) by [@ANSSI-FR](https://github.com/ANSSI-FR) * [SatochipApplet](https://github.com/Toporin/SatochipApplet) (Bitcoin Hardware Wallet) by [@Toporin](https://github.com/Toporin) * [SIMple-ID](https://github.com/alan-turing-institute/SIMple-ID) by [@alan-turing-institute](https://github.com/alan-turing-institute) * [HelloSTK2](https://github.com/mrlnc/HelloSTK2) (SIM toolkit sample app) by [@mrlnc](https://github.com/mrlnc) * [NeoPGP](https://github.com/mwalle/NeoPGP) by [@mwalle](https://github.com/mwalle) * [EUCLEAK](https://ninjalab.io/eucleak/) by [NinjaLab](https://ninjalab.io) * Plus loads of academic projects, classes and papers. * Integration projects: * [JavaCard Gradle plugin](https://github.com/bertrandmartel/javacard-gradle-plugin) by [@bertrandmartel](https://github.com/bertrandmartel) * [JavaCard Template project with Gradle](https://github.com/ph4r05/javacard-gradle-template) by [@ph4r05](https://github.com/ph4r05) * [ant-javacard in Docker](https://github.com/xoryouyou/docker-javacard) by [@xoryouyou](https://github.com/xoryouyou) * Other: * **You!** Don't torture yourself with complexity, **KISS!** ## Contact * See [javacard.pro](https://javacard.pro) * [GlobalPlatform/JavaCard discussions](https://github.com/martinpaljak/GlobalPlatformPro/discussions) ## Similar projects * standard JavaCard SDK Ant tasks * :( as cumbersome to use as the command line utilities * :( not declarative/DWIM enough * :) very explicit interface with all details exposed * JavaCard Gradle plugin (MIT) - https://github.com/bertrandmartel/javacard-gradle-plugin * :) Wraps ant-javacard for use with Gradle * gradle-javacard (Apache 2.0) - https://github.com/fidesmo/gradle-javacard * :) nice declarative interface * :( requires gradle (40M download) * :( JavaCard 2.2.2 only * EclipseJCDE (Eclipse 1.0) - http://eclipse-jcde.sourceforge.net/ * :( JavaCard 2.2.2 only * :( not possible to integrate in CI - depends on eclipse * :( essentially an Eclipse GUI wrapper for JC SDK * JCOP Tools * :( not open source * NetBeans IDE JC support * :( not possible to integrate into CI * :( JavaCard 3.0 only * :( Netbeans, not cross platform * Maven2 task from FedICT (LGPL3) - https://code.google.com/p/eid-quick-key-toolset * :( Maven downloads half the internet before even simple tasks * :( JavaCard 2.2.2 only * Ant script files with templates * :( XML is a *very* bad and verbose programming environment ================================================ FILE: REUSE.toml ================================================ version = 1 # Project-owned configuration, build, and documentation files. # These are not Java source and do not carry per-file SPDX comments; # bulk-declared here per REUSE.software v3.3. [[annotations]] path = [ "LICENSE", "Makefile", "README.md", ".gitignore", ".gitmodules", ".github/**", "pom.xml", "**/pom.xml", "build.xml", "**/spotbugs.xml", "tests.xml", "tests-fail.xml", "tests-1.8.xml", "tests-11.xml", "tests-17.xml", "tests-21.xml", "kits.xml", "fails.xml", "capfile/src/main/resources/**", ] precedence = "aggregate" SPDX-FileCopyrightText = "2015 Martin Paljak " SPDX-License-Identifier = "MIT" # Maven Wrapper - vendored from the Apache Maven project (Apache-2.0). [[annotations]] path = [ "mvnw", "mvnw.cmd", ".mvn/wrapper/**", ] precedence = "aggregate" SPDX-FileCopyrightText = "The Apache Software Foundation" SPDX-License-Identifier = "Apache-2.0" ================================================ FILE: build.xml ================================================ ${repository.version} @ ${repository.timestamp} ================================================ FILE: capfile/pom.xml ================================================ 4.0.0 com.github.martinpaljak ant-javacard-package 26.03.08-SNAPSHOT capfile JavaCard SDK and CAP file library 2026-03-08T04:57:03Z org.testng testng 7.12.0 test org.slf4j slf4j-api 2.0.17 test org.slf4j slf4j-simple 2.0.17 test ================================================ FILE: capfile/spotbugs.xml ================================================ ================================================ FILE: capfile/src/main/java/module-info.java ================================================ // SPDX-FileCopyrightText: 2024 Martin Paljak // SPDX-License-Identifier: MIT @SuppressWarnings({"requires-automatic"}) module pro.javacard.capfile { requires java.xml; requires java.logging; exports pro.javacard.capfile; exports pro.javacard.sdk; } ================================================ FILE: capfile/src/main/java/pro/javacard/capfile/AID.java ================================================ // SPDX-FileCopyrightText: 2018 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.capfile; import java.util.Arrays; public final class AID { private final byte[] bytes; public AID(byte[] bytes) throws IllegalArgumentException { this(bytes, 0, bytes.length); } public AID(String str) throws IllegalArgumentException { this(HexUtils.hex2bin(str)); } public AID(byte[] bytes, int offset, int length) throws IllegalArgumentException { if ((length < 5) || (length > 16)) { throw new IllegalArgumentException("AID must be between 5 and 16 bytes: " + length); } this.bytes = Arrays.copyOfRange(bytes, offset, offset + length); } public static AID fromString(Object s) { if (s instanceof String) { return new AID(HexUtils.stringToBin((String) s)); } throw new IllegalArgumentException("AID should be string"); } public byte[] getBytes() { return bytes.clone(); } public int getLength() { return bytes.length; } @Override public String toString() { return HexUtils.bin2hex(bytes); } @Override public int hashCode() { return Arrays.hashCode(bytes); } @Override public boolean equals(Object o) { if (o instanceof AID) { return Arrays.equals(((AID) o).bytes, bytes); } return false; } } ================================================ FILE: capfile/src/main/java/pro/javacard/capfile/CAPFile.java ================================================ // SPDX-FileCopyrightText: 2018 Martin Paljak // SPDX-License-Identifier: MIT // Loosely based on code from GlobalPlatformPro, originally from GPJ package pro.javacard.capfile; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.*; import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; /** * Parses a CAP file as specified in JavaCard 2.2 VM Specification, chapter 6. * CAP files are tiny, so we keep it in memory. */ public final class CAPFile { private static final String[] componentNames = {"Header", "Directory", "Import", "Applet", "Class", "Method", "StaticField", "Export", "ConstantPool", "RefLocation", "Descriptor", "Debug"}; protected final Map entries; // All raw ZIP entries // Parsed content private final Map applets = new LinkedHashMap<>(); private final List imports = new ArrayList<>(); private final CAPPackage pkg; private final byte flags; private final String cap_version; // Metadata private Manifest manifest = null; // From 2.2.2 private Document appletxml = null; // From 3.0.1 private Path file; public static CAPFile fromStream(InputStream in) throws IOException { return new CAPFile(in); } public static CAPFile fromBytes(byte[] bytes) throws IOException { return fromStream(new ByteArrayInputStream(bytes)); } public static CAPFile fromFile(Path path) throws IOException { try (InputStream in = Files.newInputStream(path)) { CAPFile cap = fromStream(in); cap.file = path; return cap; } } public Optional getFile() { return Optional.ofNullable(file); } public byte[] getComponent(String name) { byte[] c = entries.get(pkg2jcdir(getPackageName()) + name + ".cap"); return c == null ? null : c.clone(); } public byte[] getMetaInfEntry(String name) { return entries.get("META-INF/" + name); } public Optional getZipComponent(String name) { return Optional.ofNullable(entries.get(name)); } public void store(OutputStream to) throws IOException { try (ZipOutputStream out = new ZipOutputStream(to)) { for (Map.Entry e : entries.entrySet()) { out.putNextEntry(new ZipEntry(e.getKey())); out.write(e.getValue()); out.closeEntry(); } } } // XXX: 21 rightfully complains about this without final (getComponent leaking this) protected CAPFile(InputStream in) throws IOException { try (ZipInputStream zip = new ZipInputStream(in)) { // All ZIP entries entries = readEntries(zip); } // Parse manifest byte[] mf = entries.get("META-INF/MANIFEST.MF"); if (mf != null) { ByteArrayInputStream mfi = new ByteArrayInputStream(mf); manifest = new Manifest(mfi); } // Only if there are applets byte[] ai = entries.get("APPLET-INF/applet.xml"); if (ai != null) { try { DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); // Not really a threat (intended for self-generated local files) but still nice to have dbFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); appletxml = dBuilder.parse(new ByteArrayInputStream(ai)); appletxml.getDocumentElement().normalize(); } catch (SAXException | ParserConfigurationException e) { throw new IOException(e); } } // Figure out package name. Failsafe without metadata as well, for 2.1.X support. String pkgname = null; for (String p : entries.keySet()) { if (p.endsWith("Header.cap")) { pkgname = jcdir2pkg(p); break; } } if (pkgname == null) { throw new IOException("Could not figure out the package name of the applet!"); } // Parse package. // See JCVM 2.2 spec section 6.3 for offsets. byte[] header = entries.get(pkg2jcdir(pkgname) + "Header.cap"); cap_version = String.format("%d.%d", header[8], header[7]); flags = header[9]; pkg = new CAPPackage(new AID(header, 13, header[12]), header[11], header[10], pkgname); // Parse applets // See JCVM 2.2 spec section 6.5 for offsets. byte[] applet = getComponent("Applet"); if (applet != null) { int offset = 4; for (int j = 0; j < (applet[3] & 0xFF); j++) { int len = applet[offset++]; AID appaid = new AID(applet, offset, len); // We might already have it, with the name from metadata // FIXME: use metadata only as additional source if (!applets.containsKey(appaid)) { applets.put(appaid, null); } // Skip install_method_offset offset += len + 2; } } // Parse imports byte[] imps = getComponent("Import"); if (imps != null) { int offset = 4; for (int j = 0; j < (imps[3] & 0xFF); j++) { AID aid = new AID(imps, offset + 3, imps[offset + 2]); CAPPackage p = new CAPPackage(aid, imps[offset + 1], imps[offset]); imports.add(p); offset += imps[offset + 2] + 3; } } // Parse metadata to get applet names. Somewhat redundant if (appletxml != null) { NodeList apps = appletxml.getElementsByTagName("applet"); for (int i = 0; i < apps.getLength(); i++) { Element app = (Element) apps.item(i); String name = app.getElementsByTagName("applet-class").item(0).getTextContent(); String aidstring = app.getElementsByTagName("applet-AID").item(0).getTextContent(); AID aid = AID.fromString(aidstring.replace("//aid/", "").replace("/", "")); if (!applets.containsKey(aid)) { throw new IOException("applet.xml contains missing applet " + aid); } applets.put(aid, name); } } } private static Map readEntries(ZipInputStream in) throws IOException { Map result = new LinkedHashMap<>(); ZipEntry entry = in.getNextEntry(); while (entry != null) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; int c; while ((c = in.read(buf)) != -1) { bos.write(buf, 0, c); } result.put(entry.getName(), bos.toByteArray()); entry = in.getNextEntry(); } return Collections.unmodifiableMap(result); } public AID getPackageAID() { return pkg.aid; } public List getAppletAIDs() { List result = new ArrayList<>(); result.addAll(applets.keySet()); return result; } public String getPackageVersion() { return pkg.getVersionString(); } public String getPackageName() { return pkg.getName().orElseThrow(() -> new IllegalStateException("No package name")); } public byte[] getCode() { return _getCode(false); } @Deprecated public byte[] getCode(boolean includeDebug) { return _getCode(includeDebug); } byte[] _getCode(boolean includeDebug) { ByteArrayOutputStream result = new ByteArrayOutputStream(); for (String name : componentNames) { byte[] c = getComponent(name); if (c == null) { continue; } if (!includeDebug && (name.equals("Debug") || name.equals("Descriptor"))) { continue; } try { result.write(c); } catch (IOException e) { throw new UncheckedIOException(e); } } return result.toByteArray(); } public byte[] getLoadFileDataHash(String hash) { try { return MessageDigest.getInstance(hash).digest(getCode()); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("Not possible", e); } } @Deprecated public byte[] getLoadFileDataHash(String hash, boolean includeDebug) { try { return MessageDigest.getInstance(hash).digest(_getCode(includeDebug)); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("Not possible", e); } } public void dump(PrintStream out) { Optional gpv = guessGlobalPlatformVersion(); Optional jcv = guessJavaCardVersion(); String gpversion = gpv.isPresent() ? "/GlobalPlatform " + gpv.get() : ""; out.println(String.format("CAP file (v%s), contains: %s for JavaCard %s%s", cap_version, String.join(", ", getFlags()), jcv.orElse("2.1.1?"), gpversion)); out.printf("Package: %s %s v%s%n", pkg.getName().get(), pkg.getAid().toString(), pkg.getVersionString()); for (Map.Entry applet : getApplets().entrySet()) { out.println("Applet: " + (applet.getValue() == null ? "" : applet.getValue() + " ") + applet.getKey()); } for (CAPPackage imp : getImports()) { out.println("Import: " + imp); } // Check manifest for metadata if (manifest != null) { Attributes mains = manifest.getMainAttributes(); // iterate all packages Map ent = manifest.getEntries(); if (ent.keySet().size() > 1) { throw new IllegalArgumentException("Too many elements in CAP manifest"); } if (ent.keySet().size() == 1) { Attributes caps = ent.get(ent.keySet().toArray()[0]); // Generic String jdk_name = mains.getValue("Created-By"); // JC specific String cap_creation_time = caps.getValue("Java-Card-CAP-Creation-Time"); String converter_version = caps.getValue("Java-Card-Converter-Version"); String converter_provider = caps.getValue("Java-Card-Converter-Provider"); out.println(String.format("Generated by %s converter %s", converter_provider, converter_version)); out.println(String.format("On %s with JDK %s", cap_creation_time, jdk_name)); } } out.println(String.format("Code size %d bytes (%d with debug)", getCode().length, getCode(true).length)); out.println("SHA-256 " + HexUtils.bin2hex(getLoadFileDataHash("SHA-256")).toLowerCase()); out.println("SHA-1 " + HexUtils.bin2hex(getLoadFileDataHash("SHA-1")).toLowerCase()); } public List getFlags() { return flags2strings(flags); } public static List flags2strings(byte flags) { ArrayList result = new ArrayList<>(); // Table 6-3: CAP File Package Flags if ((flags & 0x01) == 0x01) { result.add("integers"); } if ((flags & 0x02) == 0x02) { result.add("exports"); } if ((flags & 0x04) == 0x04) { result.add("applets"); } return result; } public List getImports() { return Collections.unmodifiableList(imports); } public Map getApplets() { return Collections.unmodifiableMap(applets); } // Guess the targeted JavaCard version based on imported package versions. // // Mapping derived from parsing export files in actual SDK kits (jc211 through jc320v25.1): // framework 1.0=2.1.x, 1.2=2.2.1, 1.3=2.2.2, 1.4=3.0.1, 1.5=3.0.4, 1.6=3.0.5, 1.8=3.1.0, 1.9=3.2.0 // security/crypto 1.1=2.1.x, 1.2=2.2.1, 1.3=2.2.2, 1.4=3.0.1, 1.5=3.0.4, 1.6=3.0.5, 1.7=3.1.0, 1.8=3.2.0 // Note: framework minor versions diverge from security/crypto starting from 3.1.0 (framework skips minor=7). // Note: SDK 2.1.1 and 2.1.2 ship identical module versions - indistinguishable. public Optional guessJavaCardVersion() { // Primary: javacard.framework has a unique version progression (skips minor=7) AID jf = new AID("A0000000620101"); // javacard.framework for (CAPPackage p : imports) { if (p.aid.equals(jf)) { switch (p.minor) { case 0: return Optional.of("2.1.1"); case 1: // No actual SDK ships framework 1.1; kept for historical reasons return Optional.of("2.1.2"); case 2: return Optional.of("2.2.1"); case 3: return Optional.of("2.2.2"); case 4: return Optional.of("3.0.1"); case 5: return Optional.of("3.0.4"); case 6: return Optional.of("3.0.5"); // minor=7 not used by any SDK case 8: return Optional.of("3.1.0"); case 9: return Optional.of("3.2.0"); default: return Optional.of(String.format("unknown: %d.%d", p.major, p.minor)); } } } // Fallback: javacard.security and javacardx.crypto share identical version progression AID js = new AID("A0000000620102"); // javacard.security AID jc = new AID("A0000000620201"); // javacardx.crypto for (CAPPackage p : imports) { if (p.aid.equals(js) || p.aid.equals(jc)) { switch (p.minor) { case 1: return Optional.of("2.1.1"); case 2: return Optional.of("2.2.1"); case 3: return Optional.of("2.2.2"); case 4: return Optional.of("3.0.1"); case 5: return Optional.of("3.0.4"); case 6: return Optional.of("3.0.5"); case 7: return Optional.of("3.1.0"); case 8: return Optional.of("3.2.0"); default: return Optional.of(String.format("unknown: %d.%d", p.major, p.minor)); } } } return Optional.empty(); } // Guess GP version from org.globalplatform import version (A00000015100) public Optional guessGlobalPlatformVersion() { AID jf = new AID("A00000015100"); for (CAPPackage p : imports) { if (p.aid.equals(jf) && p.major == 1) { if (p.minor == 0) { return Optional.of("2.1.1"); } else if (p.minor >= 1 && p.minor <= 4) { return Optional.of("2.2"); } else if (p.minor == 5 || p.minor == 6) { return Optional.of("2.2.1"); } else if (p.minor == 7) { // This is not really right, but a good indication nevertheless return Optional.of("2.3.1+A"); } else { return Optional.of(String.format("unknown: %d.%d", p.major, p.minor)); } } } return Optional.empty(); } private static String pkg2jcdir(String pkgname) { return pkgname.replace(".", "/") + "/javacard/"; } private static String jcdir2pkg(String jcdir) { return jcdir.substring(0, jcdir.lastIndexOf("/javacard/")).replace('/', '.'); } public static void uncheckedDelete(Path p) throws UncheckedIOException { try { Files.delete(p); } catch (IOException e) { throw new UncheckedIOException(e); } } // Remove compiled code from capfile public static void strip(Path cap) throws IOException { Map props = new HashMap<>(); props.put("create", "false"); URI zip_disk = URI.create("jar:" + cap.toUri()); try (FileSystem zipfs = FileSystems.newFileSystem(zip_disk, props)) { List toDelete = Files.walk(zipfs.getPath("/")).filter(p -> p.toString().endsWith(".class")).collect(Collectors.toList()); Collections.sort(toDelete, Collections.reverseOrder(Comparator.comparingInt(o -> o.toString().length()))); toDelete.stream().forEach(CAPFile::uncheckedDelete); } } } ================================================ FILE: capfile/src/main/java/pro/javacard/capfile/CAPPackage.java ================================================ // SPDX-FileCopyrightText: 2018 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.capfile; import java.util.Objects; import java.util.Optional; public final class CAPPackage { final AID aid; final int major; final int minor; final String name; public CAPPackage(AID aid, int major, int minor) { this(aid, major, minor, null); } public CAPPackage(AID aid, int major, int minor, String name) { this.aid = aid; this.major = major; this.minor = minor; this.name = name; } @Override public boolean equals(Object other) { if (other instanceof CAPPackage) { CAPPackage o = (CAPPackage) other; return aid.equals(o.aid) && major == o.major && minor == o.minor; } return false; } @Override public int hashCode() { return Objects.hash(aid, major, minor); } @Override public String toString() { return String.format("%-32s v%d.%d %s", aid, major, minor, getName().orElse(WellKnownAID.getName(aid).orElse("(unknown)"))); } public String getVersionString() { return String.format("%d.%d", major, minor); } public AID getAid() { return aid; } public int getMinor() { return minor; } public int getMajor() { return major; } public Optional getName() { return Optional.ofNullable(name); } } ================================================ FILE: capfile/src/main/java/pro/javacard/capfile/HexUtils.java ================================================ // SPDX-FileCopyrightText: 2016 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.capfile; public class HexUtils { private HexUtils() {} // This code has been taken from Apache commons-codec 1.7 (License: Apache 2.0) private static final char[] UPPER_HEX = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; public static String encodeHexString_imp(final byte[] data) { final int l = data.length; final char[] out = new char[l << 1]; // two characters form the hex value. for (int i = 0, j = 0; i < l; i++) { out[j++] = UPPER_HEX[(0xF0 & data[i]) >>> 4]; out[j++] = UPPER_HEX[0x0F & data[i]]; } return new String(out); } public static byte[] decodeHexString_imp(String str) { char data[] = str.toCharArray(); final int len = data.length; if ((len & 0x01) != 0) { throw new IllegalArgumentException("Odd number of characters: " + str); } final byte[] out = new byte[len >> 1]; // two characters form the hex value. for (int i = 0, j = 0; j < len; i++) { int f = Character.digit(data[j], 16) << 4; if (f < 0) { throw new IllegalArgumentException("Illegal hex: " + data[j]); } j++; f = f | Character.digit(data[j], 16); if (f < 0) { throw new IllegalArgumentException("Illegal hex: " + data[j]); } j++; out[i] = (byte) (f & 0xFF); } return out; } // End of copied code from commons-codec public static byte[] hex2bin(final String hex) { return decodeHexString_imp(hex); } public static String bin2hex(final byte[] bin) { return encodeHexString_imp(bin); } public static byte[] stringToBin(String s) { s = s.toUpperCase().replaceAll(" ", "").replaceAll(":", ""); s = s.replaceAll("0X", "").replaceAll("\n", "").replaceAll("\t", ""); s = s.replaceAll(";", ""); return decodeHexString_imp(s); } } ================================================ FILE: capfile/src/main/java/pro/javacard/capfile/WellKnownAID.java ================================================ // SPDX-FileCopyrightText: 2018 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.capfile; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; import java.util.Properties; // Static class for translating AID-s into human-readable form public final class WellKnownAID { private static final Map javaCardRegistry = new LinkedHashMap<>(); private static final Map wellKnownRegistry = new LinkedHashMap<>(); private WellKnownAID() {} static { // Copied from https://stackoverflow.com/questions/25031338/how-to-get-javacard-version-on-card/25063015#25063015 // Extended and verified against JC SDK exp files javaCardRegistry.put(AID.fromString("A0000000620001"), "java.lang"); javaCardRegistry.put(AID.fromString("A0000000620002"), "java.io"); javaCardRegistry.put(AID.fromString("A0000000620003"), "java.rmi"); javaCardRegistry.put(AID.fromString("A0000000620101"), "javacard.framework"); javaCardRegistry.put(AID.fromString("A000000062010101"), "javacard.framework.service"); javaCardRegistry.put(AID.fromString("A0000000620102"), "javacard.security"); javaCardRegistry.put(AID.fromString("A0000000620201"), "javacardx.crypto"); javaCardRegistry.put(AID.fromString("A0000000620202"), "javacardx.biometry"); javaCardRegistry.put(AID.fromString("A0000000620203"), "javacardx.external"); javaCardRegistry.put(AID.fromString("A0000000620204"), "javacardx.biometry1toN"); javaCardRegistry.put(AID.fromString("A0000000620205"), "javacardx.security"); javaCardRegistry.put(AID.fromString("A000000062020501"), "javacardx.security.cert"); javaCardRegistry.put(AID.fromString("A000000062020502"), "javacardx.security.derivation"); javaCardRegistry.put(AID.fromString("A000000062020503"), "javacardx.security.util"); javaCardRegistry.put(AID.fromString("A000000062020801"), "javacardx.framework.util"); javaCardRegistry.put(AID.fromString("A00000006202080101"), "javacardx.framework.util.intx"); javaCardRegistry.put(AID.fromString("A000000062020802"), "javacardx.framework.math"); javaCardRegistry.put(AID.fromString("A000000062020803"), "javacardx.framework.tlv"); javaCardRegistry.put(AID.fromString("A000000062020804"), "javacardx.framework.string"); javaCardRegistry.put(AID.fromString("A000000062020805"), "javacardx.framework.event"); javaCardRegistry.put(AID.fromString("A000000062020806"), "javacardx.framework.nio"); javaCardRegistry.put(AID.fromString("A000000062020807"), "javacardx.framework.time"); javaCardRegistry.put(AID.fromString("A0000000620209"), "javacardx.apdu"); javaCardRegistry.put(AID.fromString("A000000062020901"), "javacardx.apdu.util"); // Other well-known AID-s wellKnownRegistry.put(AID.fromString("A00000015100"), "org.globalplatform"); wellKnownRegistry.put(AID.fromString("A00000015102"), "org.globalplatform.contactless"); wellKnownRegistry.put(AID.fromString("A0000000030000"), "visa.openplatform"); // Global Platform SSD wellKnownRegistry.put(AID.fromString("A0000001515350"), "SSD creation package"); wellKnownRegistry.put(AID.fromString("A000000151535041"), "SSD creation applet"); // Global Platform CRS (Contactless Registry Service) - Amendment C wellKnownRegistry.put(AID.fromString("A000000151435253"), "CRS package"); wellKnownRegistry.put(AID.fromString("A00000015143525300"), "CRS application"); wellKnownRegistry.put(AID.fromString("A0000000090003FFFFFFFF8910710001"), "sim.access"); wellKnownRegistry.put(AID.fromString("A0000000090003FFFFFFFF8910710002"), "sim.toolkit"); wellKnownRegistry.put(AID.fromString("A0000000090005FFFFFFFF8916010000"), "uicc.hci.framework"); wellKnownRegistry.put(AID.fromString("A0000000090005FFFFFFFF8916020100"), "uicc.hci.services.cardemulation"); wellKnownRegistry.put(AID.fromString("A0000000090005FFFFFFFF8916020200"), "uicc.hci.services.connectivity"); // Load internal try (InputStream in = WellKnownAID.class.getResourceAsStream("aid_list.properties")) { // If run differently, might not have the list if (in != null) { load(in); } } catch (IOException e) { throw new RuntimeException("Can not load builtin list of AID-s: " + e.getMessage(), e); } // Try to load more Path p = Paths.get(System.getenv().getOrDefault("AID_LIST", Paths.get(System.getProperty("user.home"), ".apdu4j", "aid_list.properties").toString())); load(p); } public static void load(InputStream in) { // FIXME: add logging instead of system.err try { Properties props = new Properties(); props.load(in); for (String k: props.stringPropertyNames()) { wellKnownRegistry.put(AID.fromString(k), props.getProperty(k)); } } catch (ClassCastException e) { System.err.println("Invalid format: " + e.getMessage()); } catch (IOException e) { System.out.println("Could not parse AID list: " + e.getMessage()); } } public static void load(Path p) { if (!Files.exists(p)) { return; } try (InputStream in = Files.newInputStream(p)) { load(in); } catch (IOException e) { System.err.println("Could not parse AID list: " + e.getMessage()); } } public static String getJavaCardName(AID aid) { return javaCardRegistry.get(aid); } public static Optional getName(AID aid) { return Optional.ofNullable(wellKnownRegistry.getOrDefault(aid, javaCardRegistry.get(aid))); } } ================================================ FILE: capfile/src/main/java/pro/javacard/sdk/ExportFileHelper.java ================================================ // SPDX-FileCopyrightText: 2024 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.sdk; import pro.javacard.capfile.HexUtils; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; // Export file format: JCVM Spec v3.2, Chapter 5 "The Export File Format" public final class ExportFileHelper { // JCVM 5.5: "The magic item contains the magic number identifying the ExportFile format; it has the value 0x00FACADE." static final int MAGIC = 0x00FACADE; // JCVM 5.6, Table 5-1: Export File Constant Pool Tags static final int TAG_UTF8 = 1; static final int TAG_INTEGER = 3; static final int TAG_CLASSREF = 7; static final int TAG_PACKAGE = 13; public enum ExportFileVersion { V21, V22, V23 } public static final class PackageInfo { private final ExportFileVersion version; private final String name; private final byte[] aid; private final int major; private final int minor; private final boolean library; PackageInfo(ExportFileVersion version, String name, byte[] aid, int major, int minor, boolean library) { this.version = version; this.name = name; this.aid = aid.clone(); this.major = major; this.minor = minor; this.library = library; } public ExportFileVersion getVersion() { return version; } public String getName() { return name; } public byte[] getAid() { return aid.clone(); } public int getMajor() { return major; } public int getMinor() { return minor; } public String getPackageVersion() { return String.format("%d.%d", major, minor); } public boolean isLibrary() { return library; } @Override public String toString() { return String.format("%s %s%s v%s (%s)", name, HexUtils.bin2hex(aid), library ? " library" : "", getPackageVersion(), version); } } private ExportFileHelper() { } public static PackageInfo parsePackage(Path path) throws IOException { try (InputStream in = Files.newInputStream(path)) { return parsePackage(in); } } public static PackageInfo parsePackage(InputStream in) throws IOException { DataInputStream dis = new DataInputStream(in); // JCVM 5.5: magic (u4) int magic = dis.readInt(); if (magic != MAGIC) { throw new IllegalArgumentException(String.format("Bad magic: 0x%08X", magic)); } // JCVM 5.5: minor_version (u1), major_version (u1) byte fileMinor = dis.readByte(); byte fileMajor = dis.readByte(); if (fileMajor != 2) { throw new IllegalArgumentException("Invalid export file major version: " + fileMajor); } ExportFileVersion version = parseFileVersion(fileMinor); // JCVM 5.5: constant_pool_count (u2) int cpCount = dis.readUnsignedShort(); // JCVM 5.6: constant_pool[] Object[] pool = new Object[cpCount]; // Store all CONSTANT_Package entries by index, since this_package // tells us which one is the actual exported package Map pkgEntries = new HashMap<>(); // index -> [flags, nameIndex, minor, major] Map pkgAids = new HashMap<>(); // index -> aid for (int i = 0; i < cpCount; i++) { int tag = dis.readUnsignedByte(); switch (tag) { case TAG_UTF8: { // JCVM 5.6.4: length (u2), bytes[length] int len = dis.readUnsignedShort(); byte[] bytes = new byte[len]; dis.readFully(bytes); pool[i] = new String(bytes, "UTF-8"); break; } case TAG_INTEGER: { // JCVM 5.6.3: bytes (u4) dis.readInt(); break; } case TAG_CLASSREF: { // JCVM 5.6.2: name_index (u2) dis.readUnsignedShort(); break; } case TAG_PACKAGE: { // JCVM 5.6.1: flags (u1), name_index (u2), // minor_version (u1), major_version (u1), aid_length (u1), aid[aid_length] int flags = dis.readUnsignedByte(); int nameIndex = dis.readUnsignedShort(); int minor = dis.readUnsignedByte(); int major = dis.readUnsignedByte(); int aidLen = dis.readUnsignedByte(); byte[] aid = new byte[aidLen]; dis.readFully(aid); pkgEntries.put(i, new int[]{flags, nameIndex, minor, major}); pkgAids.put(i, aid); break; } default: throw new IllegalArgumentException(String.format("Unknown constant pool tag: %d at index %d", tag, i)); } } // JCVM 5.5: this_package (u2) - index into constant pool identifying the exported package int thisPackage = dis.readUnsignedShort(); if (!pkgEntries.containsKey(thisPackage)) { throw new IllegalArgumentException(String.format("this_package index %d does not point to a CONSTANT_Package", thisPackage)); } int[] pkg = pkgEntries.get(thisPackage); int pkgFlags = pkg[0]; int pkgNameIndex = pkg[1]; int pkgMinor = pkg[2]; int pkgMajor = pkg[3]; byte[] pkgAid = pkgAids.get(thisPackage); if (pkgNameIndex >= cpCount || !(pool[pkgNameIndex] instanceof String)) { throw new IllegalArgumentException("Invalid package name index: " + pkgNameIndex); } // JCVM 5.6.1: name_index -> CONSTANT_Utf8 with fully qualified package name using '/' String name = ((String) pool[pkgNameIndex]).replace('/', '.'); // JCVM 5.6.1, Table 5-2: "If bit 0 of the flags item is set, this package is a library" boolean library = (pkgFlags & 0x01) != 0; return new PackageInfo(version, name, pkgAid, pkgMajor, pkgMinor, library); } // JCVM 5.5: "major version has the value 2", minor 1=v2.1, 2=v2.2, 3=v2.3 private static ExportFileVersion parseFileVersion(int minor) { switch (minor) { case 1: return ExportFileVersion.V21; case 2: return ExportFileVersion.V22; case 3: return ExportFileVersion.V23; default: throw new IllegalArgumentException("Invalid export file minor version: " + minor); } } } ================================================ FILE: capfile/src/main/java/pro/javacard/sdk/JavaCardSDK.java ================================================ // SPDX-FileCopyrightText: 2015 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.sdk; import java.io.FileInputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.PrivilegedAction; import java.util.*; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public final class JavaCardSDK { public static Optional detectSDK(Path path) { if (path == null) { throw new NullPointerException("path is null"); } // Detect SDKVersion version = detectSDKVersion(path); if (version == null) { return Optional.empty(); } Path exportDir = getExportDir(version); List apiJars = getApiJars(version); List compilerJars = getCompilerJars(version); List toolJars = getToolJars(version); JavaCardSDK sdk = new JavaCardSDK(path, version, exportDir, apiJars, toolJars, compilerJars); return Optional.of(sdk); } private static SDKVersion detectSDKVersion(Path root) { SDKVersion version = null; Path libDir = root.resolve("lib"); Path tools = libDir.resolve("tools.jar"); if (Files.exists(tools)) { try (ZipFile apiZip = new ZipFile(tools.toFile())) { ZipEntry toolsver = apiZip.getEntry("com/sun/javacard/toolsversion.properties"); if (toolsver != null) { Properties verprop = new Properties(); verprop.load(apiZip.getInputStream(toolsver)); String ver = verprop.getProperty("converter.version"); switch (ver) { case "3.0.3": return SDKVersion.V301; // XXX case "3.0.4": return SDKVersion.V304; case "3.0.5": return SDKVersion.V305; case "3.1.0": return SDKVersion.V310; case "3.2.0": return SDKVersion.V320; // 24.0 case "24.1": return SDKVersion.V320_24_1; case "25.0": return SDKVersion.V320_25_0; case "25.1": return SDKVersion.V320_25_1; default: throw new IllegalStateException("Unknown SDK release: " + ver); } } } catch (IOException e) { throw new RuntimeException(e); } } else if (Files.exists(libDir.resolve("api21.jar"))) { version = SDKVersion.V212; } else if (Files.exists(root.resolve("bin").resolve("api.jar"))) { version = SDKVersion.V211; } else if (Files.exists(libDir.resolve("converter.jar"))) { // assume 2.2.1 first version = SDKVersion.V221; // test for 2.2.2 by testing api.jar Path api = libDir.resolve("api.jar"); try (ZipFile apiZip = new ZipFile(api.toFile())) { ZipEntry testEntry = apiZip.getEntry("javacardx/apdu/ExtendedLength.class"); if (testEntry != null) { version = SDKVersion.V222; } } catch (IOException e) { throw new RuntimeException(e); } } return version; } private final Path path; private final SDKVersion version; private final Path exportDir; private final List apiJars; private final List toolJars; private final List compilerJars; private JavaCardSDK(Path root, SDKVersion version, Path exportDir, List apiJars, List toolJars, List compilerJars) { this.path = root; this.version = version; this.exportDir = path.resolve(exportDir); this.apiJars = apiJars.stream().map(path::resolve).collect(Collectors.toList()); this.compilerJars = compilerJars.stream().map(path::resolve).collect(Collectors.toList()); this.toolJars = toolJars.stream().map(path::resolve).collect(Collectors.toList()); } public Path getRoot() { return path; } public SDKVersion getVersion() { return version; } public List getApiJars() { return Collections.unmodifiableList(apiJars); } public List getCompilerJars() { return Collections.unmodifiableList(compilerJars); } public List getToolJars() { return Collections.unmodifiableList(toolJars); } public Path getExportDir() { return exportDir; } // This is for build and verification tools public JavaCardSDK target(SDKVersion targetVersion) { if (version.targets.contains(targetVersion)) { List apiJars = new ArrayList<>(); apiJars.add(Paths.get("lib", String.format("api_classic-%s.jar", targetVersion.v))); apiJars.add(Paths.get("lib", String.format("api_classic_annotations-%s.jar", targetVersion.v))); Path exportPath = Paths.get(String.format("api_export_files_%s", targetVersion.v)); return new JavaCardSDK(path, targetVersion, exportPath, apiJars, toolJars, compilerJars); } else { throw new IllegalStateException(String.format("Can not target %s with %s", targetVersion, version)); } } // Returns the classloader of verifier @SuppressWarnings("removal") // AccessController public ClassLoader getClassLoader() { return java.security.AccessController.doPrivileged(new PrivilegedAction() { public URLClassLoader run() { try { if (version.equalOrNewer(SDKVersion.V301)) { return new URLClassLoader(new URL[]{path.resolve("lib").resolve("tools.jar").toUri().toURL()}, this.getClass().getClassLoader()); } else { return new URLClassLoader(new URL[]{path.resolve("lib").resolve("offcardverifier.jar").toUri().toURL()}, this.getClass().getClassLoader()); } } catch (MalformedURLException e) { throw new RuntimeException("Could not load classes: " + e.getMessage()); } } }); } public String getRelease() { if (version == SDKVersion.V305) { try { // Get verifier class Class verifier = Class.forName("com.sun.javacard.offcardverifier.Verifier", false, getClassLoader()); // Check if 3.0.5u3 (or, hopefully, later) try { verifier.getDeclaredMethod("verifyTargetPlatform", String.class); return "3.0.5u3"; } catch (NoSuchMethodException e) { // Do nothing } // Check if 3.0.5u1 try { verifier.getDeclaredMethod("verifyCap", FileInputStream.class, String.class, Vector.class); return "3.0.5u1"; } catch (NoSuchMethodException e) { // Do nothing } // Assume 3.0.5u2 otherwise return "3.0.5u2"; } catch (ReflectiveOperationException e) { throw new RuntimeException("Could not figure out SDK release: " + e.getMessage()); } } else { // No updates with older SDK-s return version.toString(); } } // All export dir paths an SDK provides: its own + one per target version public static List getAllExportDirs(SDKVersion version) { ArrayList dirs = new ArrayList(); dirs.add(getExportDir(version)); for (SDKVersion target : version.targets) { dirs.add(Paths.get("api_export_files_" + target.v)); } return dirs; } public static Path getExportDir(SDKVersion version) { switch (version) { case V212: return Paths.get("api21_export_files"); case V310: case V320: case V320_24_1: case V320_25_0: case V320_25_1: return Paths.get("api_export_files_" + version.v); default: return Paths.get("api_export_files"); } } public static List getApiJars(SDKVersion version) { List jars = new ArrayList<>(); switch (version) { case V211: jars.add(Paths.get("bin", "api.jar")); break; case V212: jars.add(Paths.get("lib", "api21.jar")); break; case V221: case V222: jars.add(Paths.get("lib", "api.jar")); break; case V301: case V304: case V305: jars.add(Paths.get("lib", "api_classic.jar")); break; case V310: case V320: case V320_24_1: case V320_25_0: case V320_25_1: jars.add(Paths.get("lib", String.format("api_classic-%s.jar", version.v))); jars.add(Paths.get("lib", String.format("api_classic_annotations-%s.jar", version.v))); break; default: throw new IllegalStateException("Unknown SDK: " + version); } // Add annotations for 3.0.4 and 3.0.5 if (version.isOneOf(SDKVersion.V304, SDKVersion.V305)) { jars.add(Paths.get("lib", "api_classic_annotations.jar")); } return jars; } public static List getToolJars(SDKVersion version) { List jars = new ArrayList<>(); if (version.isOneOf(SDKVersion.V211)) { // We don't support verification with 2.1.X, so only converter jars.add(Paths.get("bin", "converter.jar")); } else if (version.equalOrNewer(SDKVersion.V301)) { jars.add(Paths.get("lib", "tools.jar")); } else { jars.add(Paths.get("lib", "converter.jar")); jars.add(Paths.get("lib", "offcardverifier.jar")); } return jars; } public static List getCompilerJars(SDKVersion version) { List jars = new ArrayList<>(); if (version.isOneOf(SDKVersion.V304, SDKVersion.V305)) { jars.add(Paths.get("lib", "tools.jar")); jars.add(Paths.get("lib", "api_classic_annotations.jar")); } else if (version.equalOrNewer(SDKVersion.V310)) { jars.add(Paths.get("lib", "tools.jar")); jars.add(Paths.get("lib", String.format("api_classic_annotations-%s.jar", version.v))); } return jars; } @Override public boolean equals(Object o) { if (o instanceof JavaCardSDK) { JavaCardSDK other = (JavaCardSDK) o; return path.toAbsolutePath().equals(other.path.toAbsolutePath()) && version.equals(other.version) && exportDir.equals(other.exportDir); } return false; } @Override public int hashCode() { return Objects.hash(path, exportDir); } } ================================================ FILE: capfile/src/main/java/pro/javacard/sdk/OffCardVerifier.java ================================================ // SPDX-FileCopyrightText: 2018 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.sdk; import pro.javacard.capfile.CAPFile; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; import static pro.javacard.sdk.SDKVersion.*; public final class OffCardVerifier { private final JavaCardSDK sdk; public static OffCardVerifier withSDK(JavaCardSDK sdk) { // Only main method in 2.1 SDK if (sdk.getVersion().isOneOf(V211, V212)) { throw new RuntimeException("Verification is supported with JavaCard SDK 2.2.1 or later"); } return new OffCardVerifier(sdk); } private OffCardVerifier(JavaCardSDK sdk) { this.sdk = sdk; } // Verify a CAP file against a specific JavaCard target SDK and a set of EXP files public void verifyAgainst(File f, JavaCardSDK target, Vector exps) throws VerifierError, IOException { List exports = new ArrayList<>(exps.stream().map(File::toPath).collect(Collectors.toList())); exports.add(target.getExportDir()); verify(f.toPath(), exports); } public void verifyAgainst(Path f, JavaCardSDK target, List exps) throws VerifierError, IOException { // Warn about recommended usage if (target.getVersion().isOneOf(V304, V305, V310) && sdk.getVersion() != V320_25_0) { System.err.println("NB! Please use JavaCard SDK 3.2.0 / 25.0 for verifying!"); } else { if (!sdk.getRelease().equals("3.0.5u4")) { System.err.println("NB! Please use JavaCard SDK 3.0.5u4 or later for verifying!"); } } List exports = new ArrayList<>(exps.stream().collect(Collectors.toList())); exports.add(target.getExportDir()); verify(f, exports); } // Verify a given CAP file against a set of EXP files public void verify(Path f, List exps) throws VerifierError, IOException { Path tmp = Files.createTempDirectory("capfile"); try (InputStream in = Files.newInputStream(f)) { CAPFile cap = CAPFile.fromStream(in); // Get verifier class Class verifier = Class.forName("com.sun.javacard.offcardverifier.Verifier", true, sdk.getClassLoader()); // Verifier takes a vector of files, so collect final Vector expfiles = new Vector<>(); for (Path e : exps) { // collect all export files to a list if (Files.isDirectory(e)) { expfiles.addAll(Files.walk(e.toRealPath()).filter(p -> p.toString().endsWith(".exp")).map(Path::toFile).collect(Collectors.toList())); } else if (Files.isReadable(e)) { if (e.toString().endsWith(".exp")) { expfiles.add(e.toFile()); } else if (e.toString().endsWith(".jar")) { expfiles.addAll(extractExps(e, tmp).stream().map(Path::toFile).collect(Collectors.toList())); } } } String packagename = cap.getPackageName(); // XXX: calling this on SDK 25.0 would set the level from INFO to ALL, so manually revert it in finally Level logger_before = Logger.getLogger("").getLevel(); try (FileInputStream input = new FileInputStream(f.toFile())) { // 3.0.5u1 still uses old signature if (sdk.getRelease().equals("3.0.5u3") || sdk.getRelease().equals("3.0.5u2") || sdk.getVersion().equalOrNewer(V310)) { Method m = verifier.getMethod("verifyCap", File.class, String.class, Vector.class); m.invoke(null, f.toFile(), packagename, expfiles); } else { Method m = verifier.getMethod("verifyCap", FileInputStream.class, String.class, Vector.class); m.invoke(null, input, packagename, expfiles); } } catch (InvocationTargetException e) { throw new VerifierError(e.getTargetException().getMessage(), e.getTargetException()); } catch (Exception e) { throw new VerifierError("Verification failed: " + e.getMessage(), e); } finally { Level logger_now = Logger.getLogger("").getLevel(); if (!logger_before.equals(logger_now)) { System.err.println(String.format("Resetting root logger from %s back to %s", logger_now, logger_before)); Logger.getLogger("").setLevel(logger_before); } } } catch (ReflectiveOperationException | IOException e) { throw new RuntimeException("Could not run verifier: " + e.getMessage(), e); } finally { // Clean extracted exps rmminusrf(tmp); } } private static void rmminusrf(Path path) { try { Files.walk(path).sorted(Comparator.reverseOrder()).forEach(CAPFile::uncheckedDelete); } catch (FileNotFoundException | NoSuchFileException e) { // Already gone - do nothing. } catch (IOException e) { throw new RuntimeException(e); } } private static Path under(Path out, String name) { Path p = out.resolve(name).normalize().toAbsolutePath(); if (!p.startsWith(out)) { throw new IllegalArgumentException(String.format("Invalid path in JAR: %s vs %s", p, out)); } return p; } // Extracts .exp files from a jarfile to given path (temp folder) and returns the list of .exp files there public static List extractExps(Path jarfilePath, Path out) throws IOException { List exps = new ArrayList<>(); try (JarFile jarfile = new JarFile(jarfilePath.toFile())) { Enumeration entries = jarfile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry.getName().toLowerCase().endsWith(".exp")) { Path f = under(out, entry.getName()); Path dir = f.getParent(); if (dir == null) { throw new IOException("Null parent"); // spotbugs } if (!Files.isDirectory(dir)) { Files.createDirectories(dir); // throw new IOException("Failed to create folder: " + f.getParentFile()); // f = under(out, entry.getName()); } try (InputStream is = jarfile.getInputStream(entry); OutputStream fo = Files.newOutputStream(f)) { byte[] buf = new byte[1024]; while (true) { int r = is.read(buf); if (r == -1) { break; } fo.write(buf, 0, r); } } exps.add(f); } } } return exps; } } ================================================ FILE: capfile/src/main/java/pro/javacard/sdk/SDKVersion.java ================================================ // SPDX-FileCopyrightText: 2022 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.sdk; import java.util.*; public enum SDKVersion { V211("2.1.1", "1.1", null, null), V212("2.1.2", "1.1", null, null), V221("2.2.1", "1.2", null, null), V222("2.2.2", "1.5", null, null), V301("3.0.1", "1.6", null, Arrays.asList(8, 11)), V304("3.0.4", "1.6", null, Arrays.asList(8, 11)), V305("3.0.5", "1.6", null, Arrays.asList(8, 11)), // NOTE: can't use EnumSet "recursively", thus turn the List into normal HashSet in constructor V310("3.1.0", "1.7", Arrays.asList(V304, V305), Arrays.asList(8, 11, 17)), V320("3.2.0", "1.7", Arrays.asList(V304, V305, V310), Arrays.asList(8, 11, 17)), V320_24_1("3.2.0", "1.7", Arrays.asList(V304, V305, V310, V320), Arrays.asList(11, 17)), V320_25_0("3.2.0", "1.8", Arrays.asList(V304, V305, V310, V320), Arrays.asList(8, 11, 17, 21)), V320_25_1("3.2.0", "1.8", Arrays.asList(V304, V305, V310, V320), Arrays.asList(8, 11, 17, 21)); final String v; final String class_file_target; // This indicates the highest class file version edible by SDK-s converter final Set jdks; final Set targets; SDKVersion(String v, String classfile, Collection targets, List jdks) { this.v = v; this.class_file_target = classfile; this.targets = targets == null ? new HashSet<>() : new HashSet<>(targets); this.jdks = new HashSet<>(jdks == null ? Arrays.asList(8) : jdks); } @Override public String toString() { return this.v; } public Set targets() { return Collections.unmodifiableSet(this.targets); } public boolean isOneOf(SDKVersion... versions) { for (SDKVersion v : versions) { if (this.equals(v)) { return true; } } return false; } public String javaVersion() { return class_file_target; } public Set jdkVersions() { return Collections.unmodifiableSet(jdks); } public static Optional fromVersion(String versionString) { return Arrays.stream(values()).filter(ver -> ver.v.equals(versionString)).findFirst(); } public boolean equalOrNewer(SDKVersion other) { return this.ordinal() >= other.ordinal(); } } ================================================ FILE: capfile/src/main/java/pro/javacard/sdk/VerifierError.java ================================================ // SPDX-FileCopyrightText: 2018 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.sdk; public class VerifierError extends Exception { private static final long serialVersionUID = 9099882918121440945L; public VerifierError(String message, Throwable cause) { super(message, cause); } public VerifierError(String message) { super(message); } } ================================================ FILE: capfile/src/main/resources/pro/javacard/capfile/aid_list.properties ================================================ # from https://www.commoncriteriaportal.org/files/epfiles/anssi-cc-2018_32fr.pdf A0000000308000000008DB00FF=com.gemalto.javacard.eid A0000000180F000001833032=com.gemalto.mchipadv A00000001830030100000000000000FF=com.gemalto.mpcos A0000000308000000008F500FF=com.gemalto.javacard.esign 4D4F43415F536572766572=com.gemalto.moc.server A00000001830070100000000000001FF=com.gemalto.dualPSE A0000000308000000006DF00FF=com.gemalto.javacard.mspnp A000000018320A0100000000000000FF=com.gemalto.pure A00000000310=com.visa.vsdc A000000018300B0200000000000000FF=eTravel (Virtual Pkg) A00000001880000000066240FF=com.gemalto.javacard.iasclassic # from https://www.commoncriteriaportal.org/files/epfiles/ANSSI-CC-2016-23.pdf 4D4F43415F436C69656E74=com.gemalto.moc.applet # based on https://github.com/tsenger/CCU2F/blob/master/CCU2F/cap/ccu2f.cap D276000085494A434F5058=com.nxp.id.jcopx A0000003964D344D30=org.mifare4mobile.hostinterface ================================================ FILE: capfile/src/test/java/pro/javacard/capfile/TestWellKnownAID.java ================================================ // SPDX-FileCopyrightText: 2024 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.capfile; import org.testng.Assert; import org.testng.annotations.Test; import java.io.InputStream; import java.util.Optional; public class TestWellKnownAID { @Test public void testInternalList() throws Exception { try (InputStream in = WellKnownAID.class.getResourceAsStream("aid_list.properties")) { WellKnownAID.load(in); Assert.assertEquals(WellKnownAID.getName(AID.fromString("D276000085494A434F5058")), Optional.of("com.nxp.id.jcopx")); } } } ================================================ FILE: capfile/src/test/java/pro/javacard/sdk/TestExportFiles.java ================================================ // SPDX-FileCopyrightText: 2024 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.sdk; import org.testng.Assert; import org.testng.annotations.Test; import pro.javacard.capfile.HexUtils; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public class TestExportFiles { static Path sdksRoot() { return Paths.get(System.getProperty("user.dir")).getParent().resolve("sdks"); } @Test public void testParseAllExportFiles() throws Exception { int total = 0; int failures = 0; try (Stream dirs = Files.list(sdksRoot())) { List sdkDirs = dirs.filter(Files::isDirectory).sorted().collect(Collectors.toList()); for (Path dir : sdkDirs) { Optional sdk = JavaCardSDK.detectSDK(dir); Assert.assertTrue(sdk.isPresent(), "Failed to detect SDK in " + dir); List exportDirs = JavaCardSDK.getAllExportDirs(sdk.get().getVersion()); ArrayList jarPrefixes = new ArrayList(); for (Path exportDir : exportDirs) { Path fsDir = dir.resolve(exportDir); if (Files.isDirectory(fsDir)) { try (Stream walk = Files.walk(fsDir)) { List expFiles = walk.filter(p -> p.toString().endsWith(".exp")) .filter(Files::isRegularFile) .sorted() .collect(Collectors.toList()); for (Path exp : expFiles) { total++; try { ExportFileHelper.PackageInfo pkg = ExportFileHelper.parsePackage(exp); System.out.println(exp); System.out.println(" " + pkg); } catch (Exception e) { System.err.println(String.format("%s: FAILED - %s", exp, e.getMessage())); failures++; } } } } else { jarPrefixes.add(exportDir.toString() + "/"); } } if (!jarPrefixes.isEmpty()) { Path toolsJar = dir.resolve("lib").resolve("tools.jar"); if (Files.exists(toolsJar)) { try (ZipFile zf = new ZipFile(toolsJar.toFile())) { Enumeration entries = zf.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); if (!entry.getName().endsWith(".exp")) { continue; } if (jarPrefixes.stream().noneMatch(entry.getName()::startsWith)) { continue; } total++; try { ExportFileHelper.PackageInfo pkg = ExportFileHelper.parsePackage(zf.getInputStream(entry)); System.out.println(String.format("%s!%s", toolsJar, entry.getName())); System.out.println(" " + pkg); } catch (Exception e) { System.err.println(String.format("%s!%s: FAILED - %s", toolsJar, entry.getName(), e.getMessage())); failures++; } } } } } } } Assert.assertTrue(total > 100, String.format("Expected at least 100 .exp files, found %d", total)); Assert.assertEquals(failures, 0, String.format("%d export files failed to parse", failures)); // Pinned assertions for specific files ExportFileHelper.PackageInfo v21 = ExportFileHelper.parsePackage( sdksRoot().resolve("jc211_kit/api_export_files/javacard/framework/javacard/framework.exp")); Assert.assertEquals(v21.getVersion(), ExportFileHelper.ExportFileVersion.V21); Assert.assertEquals(v21.getName(), "javacard.framework"); Assert.assertEquals(v21.getAid(), HexUtils.hex2bin("A0000000620101")); Assert.assertEquals(v21.getMajor(), 1); Assert.assertEquals(v21.getMinor(), 0); Assert.assertEquals(v21.getPackageVersion(), "1.0"); Assert.assertTrue(v21.isLibrary()); ExportFileHelper.PackageInfo v22 = ExportFileHelper.parsePackage( sdksRoot().resolve("jc221_kit/api_export_files/javacard/framework/service/javacard/service.exp")); Assert.assertEquals(v22.getVersion(), ExportFileHelper.ExportFileVersion.V22); Assert.assertEquals(v22.getName(), "javacard.framework.service"); Assert.assertEquals(v22.getAid(), HexUtils.hex2bin("A000000062010101")); ExportFileHelper.PackageInfo v23 = ExportFileHelper.parsePackage( sdksRoot().resolve("jc310b43_kit/api_export_files_3.0.4/java/lang/javacard/lang.exp")); Assert.assertEquals(v23.getVersion(), ExportFileHelper.ExportFileVersion.V23); Assert.assertEquals(v23.getName(), "java.lang"); Assert.assertEquals(v23.getAid(), HexUtils.hex2bin("A0000000620001")); // Same package across export file versions - same identity, different format Assert.assertEquals(v21.getName(), "javacard.framework"); Assert.assertEquals(v23.getName(), "java.lang"); // V23 export files with multiple CONSTANT_Package entries (imports + this_package) ExportFileHelper.PackageInfo v23fw = ExportFileHelper.parsePackage( sdksRoot().resolve("jc310b43_kit/api_export_files_3.1.0/javacard/framework/javacard/framework.exp")); Assert.assertEquals(v23fw.getVersion(), ExportFileHelper.ExportFileVersion.V23); Assert.assertEquals(v23fw.getName(), "javacard.framework"); Assert.assertEquals(v23fw.getAid(), HexUtils.hex2bin("A0000000620101")); Assert.assertEquals(v23fw.getMajor(), 1); Assert.assertEquals(v23fw.getMinor(), 8); ExportFileHelper.PackageInfo v23sec = ExportFileHelper.parsePackage( sdksRoot().resolve("jc310b43_kit/api_export_files_3.1.0/javacard/security/javacard/security.exp")); Assert.assertEquals(v23sec.getVersion(), ExportFileHelper.ExportFileVersion.V23); Assert.assertEquals(v23sec.getName(), "javacard.security"); Assert.assertEquals(v23sec.getAid(), HexUtils.hex2bin("A0000000620102")); Assert.assertEquals(v23sec.getMajor(), 1); Assert.assertEquals(v23sec.getMinor(), 7); // Non-library package ExportFileHelper.PackageInfo nonLib = ExportFileHelper.parsePackage( sdksRoot().resolve("jc304_kit/classic_simulator/api_export_files/com/sun/javacard/installer/javacard/installer.exp")); Assert.assertEquals(nonLib.getName(), "com.sun.javacard.installer"); Assert.assertEquals(nonLib.getAid(), HexUtils.hex2bin("A000000062030108")); Assert.assertFalse(nonLib.isLibrary()); } @Test(expectedExceptions = IllegalArgumentException.class) public void testBadMagic() throws Exception { ExportFileHelper.parsePackage(new ByteArrayInputStream(HexUtils.hex2bin("000000000102"))); } @Test(expectedExceptions = IllegalArgumentException.class) public void testBadMajorVersion() throws Exception { ExportFileHelper.parsePackage(new ByteArrayInputStream(HexUtils.hex2bin("00FACADE0103"))); } @Test(expectedExceptions = IllegalArgumentException.class) public void testBadMinorVersion() throws Exception { ExportFileHelper.parsePackage(new ByteArrayInputStream(HexUtils.hex2bin("00FACADE0402"))); } } ================================================ FILE: capfile/src/test/java/pro/javacard/sdk/TestSDKs.java ================================================ // SPDX-FileCopyrightText: 2024 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.sdk; import org.testng.Assert; import org.testng.annotations.Test; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; import java.util.stream.Stream; public class TestSDKs { static Path sdksRoot() { return Paths.get(System.getProperty("user.dir")).getParent().resolve("sdks"); } static JavaCardSDK sdk(String name) { return JavaCardSDK.detectSDK(sdksRoot().resolve(name)).orElseThrow(() -> new AssertionError("SDK not found: " + name)); } @Test public void testDetection() throws Exception { try (Stream dirs = Files.list(sdksRoot())) { dirs.forEach(dir -> { if (Files.isDirectory(dir)) { System.out.println(String.format("%s: %s", dir, JavaCardSDK.detectSDK(dir).map(JavaCardSDK::getRelease).orElse("not SDK"))); Assert.assertTrue(JavaCardSDK.detectSDK(dir).isPresent(), "Failed to detect SDK in " + dir); } }); } // Pinned version and release assertions Assert.assertEquals(sdk("jc211_kit").getVersion(), SDKVersion.V211); Assert.assertEquals(sdk("jc211_kit").getRelease(), "2.1.1"); Assert.assertEquals(sdk("jc212_kit").getVersion(), SDKVersion.V212); Assert.assertEquals(sdk("jc221_kit").getVersion(), SDKVersion.V221); Assert.assertEquals(sdk("jc222_kit").getVersion(), SDKVersion.V222); Assert.assertEquals(sdk("jc303_kit").getVersion(), SDKVersion.V301); Assert.assertEquals(sdk("jc304_kit").getVersion(), SDKVersion.V304); Assert.assertEquals(sdk("jc305u1_kit").getRelease(), "3.0.5u1"); Assert.assertEquals(sdk("jc305u2_kit").getRelease(), "3.0.5u2"); Assert.assertEquals(sdk("jc305u3_kit").getRelease(), "3.0.5u3"); Assert.assertEquals(sdk("jc305u4_kit").getRelease(), "3.0.5u3"); // u4 indistinguishable from u3 Assert.assertEquals(sdk("jc310b43_kit").getVersion(), SDKVersion.V310); Assert.assertEquals(sdk("jc320v25.1_kit").getVersion(), SDKVersion.V320_25_1); // Java class file target versions Assert.assertEquals(SDKVersion.V211.javaVersion(), "1.1"); Assert.assertEquals(SDKVersion.V222.javaVersion(), "1.5"); Assert.assertEquals(SDKVersion.V301.javaVersion(), "1.6"); Assert.assertEquals(SDKVersion.V320_25_1.javaVersion(), "1.8"); // Multi-target SDKs Assert.assertTrue(SDKVersion.V310.targets().contains(SDKVersion.V304)); Assert.assertTrue(SDKVersion.V310.targets().contains(SDKVersion.V305)); Assert.assertTrue(SDKVersion.V320.targets().contains(SDKVersion.V310)); // JDK support Assert.assertTrue(SDKVersion.V320_25_1.jdkVersions().contains(21)); // Version ordering and lookup Assert.assertTrue(SDKVersion.V320.equalOrNewer(SDKVersion.V211)); Assert.assertFalse(SDKVersion.V211.equalOrNewer(SDKVersion.V222)); Assert.assertEquals(SDKVersion.fromVersion("3.0.5"), Optional.of(SDKVersion.V305)); Assert.assertFalse(SDKVersion.fromVersion("9.9.9").isPresent()); } } ================================================ FILE: fails.xml ================================================ ================================================ FILE: kits.xml ================================================ ================================================ FILE: mvnw ================================================ #!/bin/sh # ---------------------------------------------------------------------------- # 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. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Apache Maven Wrapper startup batch script, version 3.3.4 # # Required ENV vars: # ------------------ # JAVA_HOME - location of a JDK home dir # # Optional ENV vars # ----------------- # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 # MAVEN_SKIP_RC - flag to disable loading of mavenrc files # ---------------------------------------------------------------------------- if [ -z "$MAVEN_SKIP_RC" ]; then if [ -f /usr/local/etc/mavenrc ]; then . /usr/local/etc/mavenrc fi if [ -f /etc/mavenrc ]; then . /etc/mavenrc fi if [ -f "$HOME/.mavenrc" ]; then . "$HOME/.mavenrc" fi fi # OS specific support. $var _must_ be set to either true or false. cygwin=false darwin=false mingw=false case "$(uname)" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true ;; Darwin*) darwin=true # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then JAVA_HOME="$(/usr/libexec/java_home)" export JAVA_HOME else JAVA_HOME="/Library/Java/Home" export JAVA_HOME fi fi ;; esac if [ -z "$JAVA_HOME" ]; then if [ -r /etc/gentoo-release ]; then JAVA_HOME=$(java-config --jre-home) fi fi # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin; then [ -n "$JAVA_HOME" ] \ && JAVA_HOME=$(cygpath --unix "$JAVA_HOME") [ -n "$CLASSPATH" ] \ && CLASSPATH=$(cygpath --path --unix "$CLASSPATH") fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw; then [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \ && JAVA_HOME="$( cd "$JAVA_HOME" || ( echo "cannot cd into $JAVA_HOME." >&2 exit 1 ) pwd )" fi if [ -z "$JAVA_HOME" ]; then javaExecutable="$(which javac)" if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. readLink=$(which readlink) if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then if $darwin; then javaHome="$(dirname "$javaExecutable")" javaExecutable="$(cd "$javaHome" && pwd -P)/javac" else javaExecutable="$(readlink -f "$javaExecutable")" fi javaHome="$(dirname "$javaExecutable")" javaHome=$(expr "$javaHome" : '\(.*\)/bin') JAVA_HOME="$javaHome" export JAVA_HOME fi fi fi if [ -z "$JAVACMD" ]; then if [ -n "$JAVA_HOME" ]; then if [ -x "$JAVA_HOME/jre/sh/java" ]; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi else JAVACMD="$( \unset -f command 2>/dev/null \command -v java )" fi fi if [ ! -x "$JAVACMD" ]; then echo "Error: JAVA_HOME is not defined correctly." >&2 echo " We cannot execute $JAVACMD" >&2 exit 1 fi if [ -z "$JAVA_HOME" ]; then echo "Warning: JAVA_HOME environment variable is not set." >&2 fi # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { if [ -z "$1" ]; then echo "Path not specified to find_maven_basedir" >&2 return 1 fi basedir="$1" wdir="$1" while [ "$wdir" != '/' ]; do if [ -d "$wdir"/.mvn ]; then basedir=$wdir break fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then wdir=$( cd "$wdir/.." || exit 1 pwd ) fi # end of workaround done printf '%s' "$( cd "$basedir" || exit 1 pwd )" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then # Remove \r in case we run on Windows within Git Bash # and check out the repository with auto CRLF management # enabled. Otherwise, we may read lines that are delimited with # \r\n and produce $'-Xarg\r' rather than -Xarg due to word # splitting rules. tr -s '\r\n' ' ' <"$1" fi } log() { if [ "$MVNW_VERBOSE" = true ]; then printf '%s\n' "$1" fi } BASE_DIR=$(find_maven_basedir "$(dirname "$0")") if [ -z "$BASE_DIR" ]; then exit 1 fi MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} export MAVEN_PROJECTBASEDIR log "$MAVEN_PROJECTBASEDIR" trim() { # MWRAPPER-139: # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. # Needed for removing poorly interpreted newline sequences when running in more # exotic environments such as mingw bash on Windows. printf "%s" "${1}" | tr -d '[:space:]' } ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" if [ -r "$wrapperJarPath" ]; then log "Found $wrapperJarPath" else log "Couldn't find $wrapperJarPath, downloading it ..." if [ -n "$MVNW_REPOURL" ]; then wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" else wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" fi while IFS="=" read -r key value; do case "$key" in wrapperUrl) wrapperUrl=$(trim "${value-}") break ;; esac done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" log "Downloading from: $wrapperUrl" if $cygwin; then wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") fi if command -v wget >/dev/null; then log "Found wget ... using wget" [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then wget ${QUIET:+"$QUIET"} "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" else wget ${QUIET:+"$QUIET"} --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" fi elif command -v curl >/dev/null; then log "Found curl ... using curl" [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then curl ${QUIET:+"$QUIET"} -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" else curl ${QUIET:+"$QUIET"} --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" fi else log "Falling back to using Java to download" javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" # For Cygwin, switch paths to Windows format before running javac if $cygwin; then javaSource=$(cygpath --path --windows "$javaSource") javaClass=$(cygpath --path --windows "$javaClass") fi if [ -e "$javaSource" ]; then if [ ! -e "$javaClass" ]; then log " - Compiling MavenWrapperDownloader.java ..." ("$JAVA_HOME/bin/javac" "$javaSource") fi if [ -e "$javaClass" ]; then log " - Running MavenWrapperDownloader.java ..." ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" fi fi fi fi ########################################################################################## # End of extension ########################################################################################## # If specified, validate the SHA-256 sum of the Maven wrapper jar file wrapperSha256Sum="" while IFS="=" read -r key value; do case "$key" in wrapperSha256Sum) wrapperSha256Sum=$(trim "${value-}") break ;; esac done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" if [ -n "$wrapperSha256Sum" ]; then wrapperSha256Result=false if command -v sha256sum >/dev/null; then if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c - >/dev/null 2>&1; then wrapperSha256Result=true fi elif command -v shasum >/dev/null; then if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then wrapperSha256Result=true fi else echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2 exit 1 fi if [ $wrapperSha256Result = false ]; then echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 exit 1 fi fi MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then [ -n "$JAVA_HOME" ] \ && JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") [ -n "$CLASSPATH" ] \ && CLASSPATH=$(cygpath --path --windows "$CLASSPATH") [ -n "$MAVEN_PROJECTBASEDIR" ] \ && MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") fi # Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain # shellcheck disable=SC2086 # safe args exec "$JAVACMD" \ $MAVEN_OPTS \ $MAVEN_DEBUG_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" ================================================ FILE: mvnw.cmd ================================================ @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @REM distributed with this work for additional information @REM regarding copyright ownership. The ASF licenses this file @REM to you under the Apache License, Version 2.0 (the @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM @REM http://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @REM KIND, either express or implied. See the License for the @REM specific language governing permissions and limitations @REM under the License. @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- @REM Apache Maven Wrapper startup batch script, version 3.3.4 @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files @REM ---------------------------------------------------------------------------- @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' @echo off @REM set title of command window title %0 @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* :skipRcPre @setlocal set ERROR_CODE=0 @REM To isolate internal variables from possible post scripts, we use another setlocal @setlocal @REM ==== START VALIDATION ==== if not "%JAVA_HOME%" == "" goto OkJHome echo. >&2 echo Error: JAVA_HOME not found in your environment. >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. >&2 goto error :OkJHome if exist "%JAVA_HOME%\bin\java.exe" goto init echo. >&2 echo Error: JAVA_HOME is set to an invalid directory. >&2 echo JAVA_HOME = "%JAVA_HOME%" >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. >&2 goto error @REM ==== END VALIDATION ==== :init @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". @REM Fallback to current working directory if not found. set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir set EXEC_DIR=%CD% set WDIR=%EXEC_DIR% :findBaseDir IF EXIST "%WDIR%"\.mvn goto baseDirFound cd .. IF "%WDIR%"=="%CD%" goto baseDirNotFound set WDIR=%CD% goto findBaseDir :baseDirFound set MAVEN_PROJECTBASEDIR=%WDIR% cd "%EXEC_DIR%" goto endDetectBaseDir :baseDirNotFound set MAVEN_PROJECTBASEDIR=%EXEC_DIR% cd "%EXEC_DIR%" :endDetectBaseDir IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig @setlocal EnableExtensions EnableDelayedExpansion for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% :endReadAdditionalConfig SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM This allows using the maven wrapper in projects that prohibit checking in binary data. if exist %WRAPPER_JAR% ( if "%MVNW_VERBOSE%" == "true" ( echo Found %WRAPPER_JAR% ) ) else ( if not "%MVNW_REPOURL%" == "" ( SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... echo Downloading from: %WRAPPER_URL% ) powershell -Command "&{"^ "$webclient = new-object System.Net.WebClient;"^ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ "}"^ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ "}" if "%MVNW_VERBOSE%" == "true" ( echo Finished downloading %WRAPPER_JAR% ) ) @REM End of extension @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file SET WRAPPER_SHA_256_SUM="" FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B ) IF NOT %WRAPPER_SHA_256_SUM%=="" ( powershell -Command "&{"^ "Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ " Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ " Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ " Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ " exit 1;"^ "}"^ "}" if ERRORLEVEL 1 goto error ) @REM Provide a "standardized" way to retrieve the CLI args that will @REM work with both Windows and non-Windows executions. set MAVEN_CMD_LINE_ARGS=%* %MAVEN_JAVA_EXE% ^ %JVM_CONFIG_MAVEN_PROPS% ^ %MAVEN_OPTS% ^ %MAVEN_DEBUG_OPTS% ^ -classpath %WRAPPER_JAR% ^ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end :error set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' if "%MAVEN_BATCH_PAUSE%"=="on" pause if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% cmd /C exit /B %ERROR_CODE% ================================================ FILE: pom.xml ================================================ 4.0.0 pom com.github.martinpaljak ant-javacard-package 26.03.08-SNAPSHOT Easy to use Ant task for building JavaCard applets ant-javacard package https://github.com/martinpaljak/ant-javacard UTF-8 UTF-8 2 2026-03-08T04:57:03Z javacard-pro javacard.pro https://mvn.javacard.pro/maven/ javacard-pro-snapshots https://mvn.javacard.pro/maven/SNAPSHOTS/ javacard scpexe://mvn@mvn.javacard.pro/home/mvn/maven/ javacard scpexe://mvn@mvn.javacard.pro/home/mvn/maven/SNAPSHOTS capfile task org.openrewrite.maven rewrite-maven-plugin 6.37.0 true org.openrewrite.staticanalysis.NeedBraces org.openrewrite.staticanalysis.ModifierOrder org.openrewrite.staticanalysis.FinalizePrivateFields org.openrewrite.recipe rewrite-static-analysis 2.33.1 com.github.spotbugs spotbugs-maven-plugin 4.9.8.3 spotbugs.xml org.jacoco jacoco-maven-plugin 0.8.14 org.apache.maven.wagon wagon-ssh-external 3.5.3 org.apache.maven.plugins maven-enforcer-plugin 3.6.2 enforce-maven-version enforce [11,) Project requires JDK 11+ [3.9.15,) org.apache.maven.plugins maven-compiler-plugin 3.15.0 default-compile 11 -Werror -Xlint:all -Xlint:-options default-testCompile 11 base-compile compile 8 8 module-info.java -Xlint:-options org.apache.maven.plugins maven-antrun-plugin 3.2.0 false ant-dist package run org.apache.maven.plugins maven-clean-plugin 3.5.0 org.apache.maven.plugins maven-install-plugin 3.1.4 org.apache.maven.plugins maven-deploy-plugin 3.1.4 org.apache.maven.plugins maven-site-plugin 3.21.0 org.apache.maven.plugins maven-surefire-plugin 3.5.5 org.apache.maven.plugins maven-jar-plugin 3.5.0 org.apache.maven.plugins maven-resources-plugin 3.5.0 MIT https://github.com/martinpaljak/ant-javacard/blob/master/LICENSE repo https://github.com/martinpaljak/ant-javacard check org.openrewrite.maven rewrite-maven-plugin process-sources dryRun true com.github.spotbugs spotbugs-maven-plugin verify check org.jacoco jacoco-maven-plugin prepare-agent prepare-agent report verify report fixup org.openrewrite.maven rewrite-maven-plugin process-sources run ================================================ FILE: spotbugs.xml ================================================ ================================================ FILE: src/testapplets/empty/Empty.java ================================================ // SPDX-FileCopyrightText: 2024 Martin Paljak // SPDX-License-Identifier: MIT package testapplets.empty; import javacard.framework.APDU; import javacard.framework.Applet; import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.security.CryptoException; public class Empty extends Applet { private Empty(byte[] parameters, short offset, byte length) { register(parameters, (short) (offset + 1), parameters[offset]); } public static void install(byte[] parameters, short offset, byte length) { new Empty(parameters, offset, length); } public void process(APDU apdu) throws ISOException { if (selectingApplet()) return; byte[] buffer = apdu.getBuffer(); if (buffer[ISO7816.OFFSET_LC] != (short) 6) CryptoException.throwIt((short) 0x6666); apdu.setIncomingAndReceive(); apdu.setOutgoingAndSend((short) 0, (short) 11); } } ================================================ FILE: src/testapplets/fail/Fail.java ================================================ // SPDX-FileCopyrightText: 2024 Martin Paljak // SPDX-License-Identifier: MIT package testapplets.fail; import javacard.framework.APDU; import javacard.framework.Applet; import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.security.CryptoException; public class Fail extends Applet { private Fail(byte[] parameters, short offset, byte length) { register(parameters, (short) (offset + 1), parameters[offset]); } public static void install(byte[] parameters, short offset, byte length) { new Fail(parameters, offset, length); } public void process(APDU apdu) throws ISOException { if (selectingApplet()) return; byte[] buffer = apdu.getBuffer(); short i = 5; if (buffer[i + buffer[ISO7816.OFFSET_CLA]] != (short) 6) CryptoException.throwIt((short) 0x6666); apdu.setIncomingAndReceive(); apdu.setOutgoingAndSend((short) 0, (short) 11); } } ================================================ FILE: src/testapplets/integer/EmptyInt.java ================================================ // SPDX-FileCopyrightText: 2024 Martin Paljak // SPDX-License-Identifier: MIT package testapplets.integer; import javacard.framework.APDU; import javacard.framework.Applet; import javacard.framework.ISOException; public class EmptyInt extends Applet { private EmptyInt(byte[] parameters, short offset, byte length) { int everything = 42; register(parameters, (short) (offset + 1), parameters[offset]); } public static void install(byte[] parameters, short offset, byte length) { new EmptyInt(parameters, offset, length); } public void process(APDU arg0) throws ISOException { } } ================================================ FILE: src/testapplets/library/SomeLibrary.java ================================================ // SPDX-FileCopyrightText: 2024 Martin Paljak // SPDX-License-Identifier: MIT package testapplets.library; // Just to have an import import javacard.security.RandomData; public class SomeLibrary { public static final short TRUE = (short) 0x5AA5; public static final short FALSE = (short) 0xA55A; public static short booleantest(boolean b) { return b ? TRUE : FALSE; } public static RandomData getRandom() { return RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); } } ================================================ FILE: src/testapplets/libraryuser/LibraryUser.java ================================================ // SPDX-FileCopyrightText: 2024 Martin Paljak // SPDX-License-Identifier: MIT package testapplets.libraryuser; import javacard.framework.APDU; import javacard.framework.Applet; import javacard.framework.ISOException; import testapplets.library.SomeLibrary; public class LibraryUser extends Applet { private final short value; private LibraryUser(boolean bvalue) { value = SomeLibrary.booleantest(bvalue); } public static void install(byte[] parameters, short offset, byte length) { new LibraryUser(true).register(parameters, (short) (offset + 1), parameters[offset]); } public void process(APDU arg0) throws ISOException { SomeLibrary.booleantest(true); } } ================================================ FILE: src/testapplets/multiapp/First.java ================================================ // SPDX-FileCopyrightText: 2024 Martin Paljak // SPDX-License-Identifier: MIT package testapplets.multiapp; import javacard.framework.APDU; import javacard.framework.Applet; import javacard.framework.ISOException; import javacard.security.CryptoException; public class First extends Applet { private First(byte[] parameters, short offset, byte length) { register(parameters, (short) (offset + 1), parameters[offset]); } public static void install(byte[] parameters, short offset, byte length) { new First(parameters, offset, length); } public void process(APDU arg0) throws ISOException { CryptoException.throwIt((short) 0x6666); } } ================================================ FILE: src/testapplets/multiapp/Second.java ================================================ // SPDX-FileCopyrightText: 2024 Martin Paljak // SPDX-License-Identifier: MIT package testapplets.multiapp; import javacard.framework.APDU; import javacard.framework.Applet; import javacard.framework.ISOException; import javacard.security.CryptoException; public class Second extends Applet { private Second(byte[] parameters, short offset, byte length) { register(parameters, (short) (offset + 1), parameters[offset]); } public static void install(byte[] parameters, short offset, byte length) { new Second(parameters, offset, length); } public void process(APDU arg0) throws ISOException { CryptoException.throwIt((short) 0x6666); } } ================================================ FILE: src/testapplets/stringdefs/Empty.java ================================================ // SPDX-FileCopyrightText: 2024 Martin Paljak // SPDX-License-Identifier: MIT package testapplets.stringdefs; import javacard.framework.APDU; import javacard.framework.Applet; import javacard.framework.ISOException; import javacard.security.CryptoException; import javacardx.annotations.StringDef; import javacardx.annotations.StringPool; @StringPool(value = { @StringDef(name = "hello", value = "Hello World!"), }, name = "HelloWorldStrings") public class Empty extends Applet { private Empty(byte[] parameters, short offset, byte length) { register(parameters, (short) (offset + 1), parameters[offset]); } public static void install(byte[] parameters, short offset, byte length) { new Empty(parameters, offset, length); } public void process(APDU arg0) throws ISOException { CryptoException.throwIt((short)HelloWorldStrings.hello.length); } } ================================================ FILE: task/pom.xml ================================================ 4.0.0 com.github.martinpaljak ant-javacard-package 26.03.08-SNAPSHOT ant-javacard Easy to use Ant task for building JavaCard applets ant-javacard task org.apache.ant ant 1.10.17 provided true com.github.martinpaljak capfile ${project.version} compile org.testng testng 7.12.0 test org.slf4j slf4j-simple 2.0.17 test org.apache.maven.plugins maven-jar-plugin pro.javacard.ant.DummyMain ${project.version} ================================================ FILE: task/src/main/java/pro/javacard/ant/DummyMain.java ================================================ // SPDX-FileCopyrightText: 2015 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.ant; import pro.javacard.capfile.CAPFile; import pro.javacard.capfile.HexUtils; import pro.javacard.sdk.ExportFileHelper; import pro.javacard.sdk.JavaCardSDK; import pro.javacard.sdk.OffCardVerifier; import pro.javacard.sdk.VerifierError; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.security.MessageDigest; import java.security.ProtectionDomain; import java.util.Arrays; import java.util.Vector; import java.util.stream.Collectors; public final class DummyMain { static Path rename(Path path, String template) throws IOException { CAPFile cap = CAPFile.fromFile(path); boolean isLibrary = cap.getAppletAIDs().isEmpty(); String effectiveTemplate = template != null ? template : isLibrary ? "%n_%a_%v_%h.cap" : "%n_%a_%h_%j.cap"; Path output = Paths.get(Misc.capFileName(cap, effectiveTemplate)); if (!path.toAbsolutePath().normalize().equals(output.toAbsolutePath().normalize())) { Files.copy(path, output, StandardCopyOption.REPLACE_EXISTING); } return output; } static int runcycle(String[] argv) throws IOException { Vector args = new Vector<>(Arrays.asList(argv)); if (args.size() >= 1 && args.get(0).equals("-r")) { args.remove(0); if (args.isEmpty()) { System.err.println("Usage: java -jar ant-javacard.jar -r "); return 1; } final String capfile = args.remove(0); Path path = Paths.get(capfile); if (!Files.isRegularFile(path) || !capfile.endsWith(".cap")) { System.err.println("Not a valid CAP file: " + capfile); return 1; } try { String template = System.getenv("CAP_NAME_TEMPLATE"); if (template != null && template.contains("%J")) { System.err.println("CAP_NAME_TEMPLATE must not contain %J (JDK version is unknown for rename)"); return 1; } Path output = rename(path, template); System.out.println(output); if (path.toAbsolutePath().normalize().equals(output.toAbsolutePath().normalize())) { System.out.println("Already has standard name"); } return 0; } catch (Exception e) { System.err.println(String.format("Failed to process CAP file: %s: %s", e.getClass().getSimpleName(), e.getMessage())); return 1; } } else if (args.isEmpty()) { ProtectionDomain pd = DummyMain.class.getProtectionDomain(); System.out.println(String.format("This is an ANT task (ant-javacard %s)", DummyMain.class.getPackage().getImplementationVersion())); System.out.println("Read usage instructions from https://github.com/martinpaljak/ant-javacard#syntax"); if (pd != null && pd.getCodeSource() != null && pd.getCodeSource().getLocation() != null) { try { System.out.println(); String f = pd.getCodeSource().getLocation().getPath(); Path p = Paths.get(f); byte[] sha256 = MessageDigest.getInstance("SHA-256").digest(Files.readAllBytes(p)); System.out.println(String.format("SHA256 (%s) = %s", f, HexUtils.bin2hex(sha256).toLowerCase())); } catch (Exception e) { System.out.println("Could not verify integrity: " + e.getMessage()); } } System.out.println(); System.out.println("But you can use it to dump/verify CAP files, like this:"); System.out.println("$ java -jar ant-javacard.jar "); System.out.println(); System.out.println("Or copy a CAP file with a standard name into current directory:"); System.out.println("$ java -jar ant-javacard.jar -r "); return 1; } else if (args.size() == 1) { // Simple dumping of capfile final String capfile = args.remove(0); Path path = Paths.get(capfile); if (Files.isRegularFile(path) && capfile.endsWith(".cap")) { try { CAPFile cap = CAPFile.fromBytes(Files.readAllBytes(path)); cap.dump(System.out); return 0; } catch (Exception e) { System.err.println(String.format("Failed to read/parse CAP file: %s: %s", e.getClass().getSimpleName(), e.getMessage())); return 1; } } else if (Files.isRegularFile(path) && capfile.endsWith(".exp")) { try { System.out.println(String.format("%s: %s", path, ExportFileHelper.parsePackage(path))); return 0; } catch (Exception e) { System.err.println(String.format("Failed to read/parse EXP file: %s: %s", e.getClass().getSimpleName(), e.getMessage())); return 1; } } else { System.err.println("Usage: java -jar ant-javacard.jar "); return 1; } } else { // Verification of capfile final Path sdkpath = Paths.get(args.remove(0)); // Targetsdk path is a folder final Path targetsdkpath; final String capfile; final String next = args.remove(0); if (Files.isDirectory(Paths.get(next))) { targetsdkpath = Paths.get(next); capfile = args.remove(0); } else { capfile = next; targetsdkpath = sdkpath; } // If jarfile is given, exports from jar files are extracted internally. Vector exps = args.stream().map(File::new).collect(Collectors.toCollection(Vector::new)); CAPFile cap = CAPFile.fromBytes(Files.readAllBytes(Paths.get(capfile))); try { JavaCardSDK sdk = JavaCardSDK.detectSDK(sdkpath).orElseThrow(() -> new VerifierError("No SDK detected in " + sdkpath)); JavaCardSDK target = JavaCardSDK.detectSDK(targetsdkpath).orElseThrow(() -> new VerifierError("No target SDK detected with " + targetsdkpath)); OffCardVerifier verifier = OffCardVerifier.withSDK(sdk); cap.dump(System.out); verifier.verifyAgainst(new File(capfile), target, exps); System.out.println(String.format("Verified %s with SDK v%s against SDK v%s", capfile, sdk.getVersion(), target.getVersion())); return 0; } catch (VerifierError e) { System.err.println("Verification failed: " + e.getMessage()); return 1; } } } public static void main(String[] argv) { try { runcycle(argv); } catch (Throwable e) { Misc.cleanTemp(); System.err.println(String.format("Error: %s: %s", e.getClass().getSimpleName(), e.getMessage())); if (System.getenv("ANT_JAVACARD_DEBUG") != null) { e.printStackTrace(); } System.exit(1); } } } ================================================ FILE: task/src/main/java/pro/javacard/ant/HelpingBuildException.java ================================================ // SPDX-FileCopyrightText: 2015 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.ant; import org.apache.tools.ant.BuildException; public class HelpingBuildException extends BuildException { private static final long serialVersionUID = -2365126253968479314L; public HelpingBuildException(String msg) { super(msg + "\n\nPLEASE READ https://github.com/martinpaljak/ant-javacard#readme"); } } ================================================ FILE: task/src/main/java/pro/javacard/ant/JCApplet.java ================================================ // SPDX-FileCopyrightText: 2015 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.ant; import org.apache.tools.ant.BuildException; // Just for Ant public class JCApplet { String klass = null; byte[] aid = null; public JCApplet() { } public void setClass(String msg) { klass = msg; } public void setAID(String msg) { try { aid = Misc.stringToBin(msg); if (aid.length < 5 || aid.length > 16) { throw new BuildException("Applet AID must be between 5 and 16 bytes: " + aid.length); } } catch (IllegalArgumentException e) { throw new BuildException("Not a valid applet AID: " + e.getMessage()); } } } ================================================ FILE: task/src/main/java/pro/javacard/ant/JCCap.java ================================================ // SPDX-FileCopyrightText: 2015 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.ant; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.taskdefs.Jar; import org.apache.tools.ant.taskdefs.Java; import org.apache.tools.ant.taskdefs.Javac; import org.apache.tools.ant.types.Environment; import org.apache.tools.ant.types.FileSet; import pro.javacard.capfile.CAPFile; import pro.javacard.capfile.HexUtils; import pro.javacard.sdk.JavaCardSDK; import pro.javacard.sdk.OffCardVerifier; import pro.javacard.sdk.SDKVersion; import pro.javacard.sdk.VerifierError; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.util.*; import java.util.logging.Logger; import java.util.regex.Pattern; import static pro.javacard.sdk.SDKVersion.*; // ... and actual execution of core task. public class JCCap extends Task { static final String DEFAULT_CAP_NAME_TEMPLATE = "%n_%a_%h_%j_%J.cap"; static final String DEFAULT_CAP_NAME_TEMPLATE_LIB = "%n_%a_%v_%h_%J.cap"; private final String master_jckit_path; private JavaCardSDK jckit = null; private String classes_path = null; private String sources_path = null; private String sources2_path = null; private String includes = null; private String excludes = null; private String package_name = null; private byte[] package_aid = null; private String package_version = null; private final List raw_applets = new ArrayList<>(); private final List raw_imports = new ArrayList<>(); private final List raw_sources = new ArrayList<>(); private String output_cap = null; private String output_exp = null; private String output_jar = null; private String output_jca = null; private String jckit_path = null; private JavaCardSDK targetsdk = null; private String raw_targetsdk = null; private boolean verify = true; private boolean debug = false; private boolean strip = false; private boolean ints = false; private boolean exportmap = false; static final String _logconf; static final String LOGHACK = "_ANT_JAVACARD_LOGHACK"; static final boolean loghack = Boolean.parseBoolean(System.getenv().getOrDefault(LOGHACK, "true")); static { if (loghack) { // Setting the java.util.logging configuration for convert task will prevent the creation of ~/java0.log.0 file Path logconf = Misc.makeTemp("logging").resolve("logging.properties"); _logconf = logconf.toAbsolutePath().normalize().toString(); try { Files.write(logconf, String.format("handlers = java.util.logging.ConsoleHandler%n.level = WARNING").getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } catch (IOException e) { System.err.println("Could not write temporary logging configuration: " + e.getMessage()); } } else { _logconf = null; System.err.println("Loghack disabled"); } if (System.getenv().containsKey(LOGHACK)) { System.err.println("log level at start: " + Logger.getLogger("").getLevel()); } } public JCCap(String master_jckit_path) { this.master_jckit_path = master_jckit_path; } public void setJCKit(String msg) { jckit_path = msg; } public void setOutput(String msg) { output_cap = msg; } public void setExport(String msg) { output_exp = msg; } public void setJar(String msg) { output_jar = msg; } public void setJca(String msg) { output_jca = msg; } public void setPackage(String msg) { package_name = msg; } public void setClasses(String msg) { classes_path = msg; } public void setVersion(String msg) { package_version = msg; } public void setSources(String arg) { sources_path = arg; } public void setSources2(String arg) { sources2_path = arg; } public void setIncludes(String arg) { includes = arg; } public void setExcludes(String arg) { excludes = arg; } public void setVerify(boolean arg) { verify = arg; } public void setDebug(boolean arg) { debug = arg; } public void setStrip(boolean arg) { strip = arg; } public void setInts(boolean arg) { ints = arg; } public void setExportmap(boolean arg) { exportmap = arg; } public void setTargetsdk(String arg) { raw_targetsdk = arg; } public void setAID(String msg) { try { package_aid = Misc.stringToBin(msg); if (package_aid.length < 5 || package_aid.length > 16) { throw new BuildException(String.format("Package AID must be between 5 and 16 bytes: %s (%d)", HexUtils.bin2hex(package_aid), package_aid.length)); } } catch (IllegalArgumentException e) { throw new BuildException("Not a correct package AID: " + e.getMessage()); } } // Many applets inside one package public JCApplet createApplet() { JCApplet applet = new JCApplet(); raw_applets.add(applet); return applet; } // Many imports inside one package public JCImport createImport() { JCImport imp = new JCImport(); raw_imports.add(imp); return imp; } // To support usage from Gradle, where import is a reserved name public JCImport createJimport() { return this.createImport(); } // Nested elements public JCSources createSources() { JCSources src = new JCSources(); raw_sources.add(src); return src; } private Optional findSDK() { // try local configuration first if (jckit_path != null) { return JavaCardSDK.detectSDK(getProject().resolveFile(jckit_path).toPath()); } // then try the master configuration if (master_jckit_path != null) { return JavaCardSDK.detectSDK(getProject().resolveFile(master_jckit_path).toPath()); } // now check via ant property String propPath = getProject().getProperty("jc.home"); if (propPath != null) { return JavaCardSDK.detectSDK(getProject().resolveFile(propPath).toPath()); } // finally via the environment String envPath = System.getenv("JC_HOME"); if (envPath != null) { return JavaCardSDK.detectSDK(getProject().resolveFile(envPath).toPath()); } // return null if no options return Optional.empty(); } // Check that arguments are sufficient and do some DWIM private void check() { jckit = findSDK().orElseThrow(() -> new HelpingBuildException("No usable JavaCard SDK referenced")); log("INFO: using JavaCard " + jckit.getVersion() + " SDK in " + jckit.getRoot() + " with JDK " + Misc.getCurrentJDKVersion(), Project.MSG_INFO); if (raw_targetsdk != null) { Optional targetVersion = SDKVersion.fromVersion(raw_targetsdk); if (targetVersion.isPresent() && !jckit.getVersion().targets().isEmpty()) { SDKVersion target = targetVersion.get(); if (jckit.getVersion().equals(target)) { log("WARN: \"targetsdk\" ignored as it matches \"jckit\" version", Project.MSG_WARN); } else { if (jckit.getVersion().targets().contains(target)) { targetsdk = jckit.target(target); } else { throw new HelpingBuildException(String.format("Can not target JavaCard %s with JavaCard kit %s", target, jckit.getVersion())); } } } else { // Resolve target targetsdk = JavaCardSDK.detectSDK(getProject().resolveFile(raw_targetsdk).toPath()).orElseThrow(() -> new HelpingBuildException("Invalid \"targetsdk\": " + raw_targetsdk)); // NOTE: verification will fail, as 3.1.0 (applies to all "modern multi-target" SDK-s) // will require version 2.3 export files (only available as part of newer SDK-s). // Verification is default, so fail early. // This also means that using an older SDK as path reference will actually use export files from current multi-target SDK if (!jckit.getVersion().targets().isEmpty() && !targetsdk.getVersion().equalOrNewer(V304)) { throw new HelpingBuildException(String.format("targetsdk %s is not compatible with jckit %s", targetsdk.getVersion(), jckit.getVersion())); } } } if (targetsdk == null) { targetsdk = jckit; } else { if (jckit.getRoot() != targetsdk.getRoot()) { log(String.format("INFO: targeting JavaCard %s SDK in %s", targetsdk.getVersion(), targetsdk.getRoot()), Project.MSG_INFO); } else { log("INFO: targeting JavaCard " + targetsdk.getVersion(), Project.MSG_INFO); } } // Warn about deprecation in future if (sources_path != null && sources2_path != null) { log("WARN: sources2 is deprecated in favor of multiple paths in sources", Project.MSG_WARN); } // Nested and flat sources/sources2 attributes are mutually exclusive if (!raw_sources.isEmpty() && (sources_path != null || sources2_path != null)) { throw new HelpingBuildException("Can not use both nested elements and sources/sources2 attributes"); } // Shorthand for simple small projects - use Maven conventions if (sources_path == null && classes_path == null && raw_sources.isEmpty()) { if (getProject().resolveFile("src/main/javacard").isDirectory()) { sources_path = "src/main/javacard"; } else if (getProject().resolveFile("src/main/java").isDirectory()) { sources_path = "src/main/java"; } } // sources or classes must be set if (sources_path == null && classes_path == null && raw_sources.isEmpty()) { throw new HelpingBuildException("Must specify \"sources\" or \"classes\""); } // Check package version if (package_version == null) { package_version = "0.0"; } else { // Allowed values are 0..127 if (!package_version.matches("^[0-9]{1,3}\\.[0-9]{1,3}$")) { throw new HelpingBuildException("Invalid package version: " + package_version); } if (Arrays.stream(package_version.split("\\.")).map(e -> Integer.parseInt(e, 10)).anyMatch(e -> (e < 0 || e > 127))) { throw new HelpingBuildException("Illegal package version value: " + package_version); } } // Check imports for (JCImport a : raw_imports) { if (a.jar != null && !getProject().resolveFile(a.jar).isFile()) { throw new BuildException("Import JAR does not exist: " + a.jar); } if (a.exps != null && !getProject().resolveFile(a.exps).isDirectory()) { throw new BuildException("Import EXP files folder does not exist: " + a.exps); } } // Check nested sources for (JCSources s : raw_sources) { if (s.path == null) { throw new BuildException("Nested element must have a \"path\" attribute"); } if (!getProject().resolveFile(s.path).isDirectory()) { throw new BuildException("Sources path does not exist: " + s.path); } } // Construct applets and fill in missing bits from package info, if necessary int applet_counter = 0; for (JCApplet a : raw_applets) { // Keep count for automagic numbering applet_counter = applet_counter + 1; if (a.klass == null) { throw new HelpingBuildException("Applet class is missing"); } // If package name is present, must match the applet if (package_name != null) { if (!a.klass.contains(".")) { a.klass = String.format("%s.%s", package_name, a.klass); } else if (!a.klass.startsWith(package_name)) { throw new HelpingBuildException(String.format("Applet class %s is not in package %s", a.klass, package_name)); } } else { if (a.klass.contains(".")) { String pkgname = a.klass.substring(0, a.klass.lastIndexOf(".")); log("INFO: setting package name to " + pkgname, Project.MSG_INFO); package_name = pkgname; } else { throw new HelpingBuildException("Applet must be in a package!"); } } // If applet AID is present, must match the package AID if (package_aid != null) { if (a.aid != null) { // RID-s must match if (!Arrays.equals(Arrays.copyOf(package_aid, 5), Arrays.copyOf(a.aid, 5))) { throw new HelpingBuildException("Package RID does not match Applet RID"); } } else { // make "magic" applet AID from package_aid + counter a.aid = Arrays.copyOf(package_aid, package_aid.length + 1); a.aid[package_aid.length] = (byte) applet_counter; log("INFO: generated applet AID: " + HexUtils.bin2hex(a.aid) + " for " + a.klass, Project.MSG_INFO); } } else { // if package AID is empty, just set it to the minimal from // applet if (a.aid != null) { package_aid = Arrays.copyOf(a.aid, 5); } else { throw new HelpingBuildException("Both package AID and applet AID are missing!"); } } } // Check package AID if (package_aid == null) { throw new HelpingBuildException("Must specify package AID"); } // Package name must be present if no applets if (raw_applets.isEmpty()) { if (package_name == null) { throw new HelpingBuildException("Must specify package name if no applets"); } log(String.format("Building library from package %s (AID: %s)", package_name, HexUtils.bin2hex(package_aid)), Project.MSG_INFO); } else { log(String.format("Building CAP with %d applet%s from package %s (AID: %s)", applet_counter, applet_counter > 1 ? "s" : "", package_name, HexUtils.bin2hex(package_aid)), Project.MSG_INFO); for (JCApplet app : raw_applets) { log(String.format("%s %s", app.klass, HexUtils.bin2hex(app.aid)), Project.MSG_INFO); } } if (output_exp != null) { // Last component of the package String ln = Misc.lastName(package_name); output_jar = new File(output_exp, ln + ".jar").toString(); } // Default output name if (output_cap == null) { output_cap = raw_applets.size() == 0 ? DEFAULT_CAP_NAME_TEMPLATE_LIB : DEFAULT_CAP_NAME_TEMPLATE; } } // To lessen the java.nio and apache.ant namespace clash... private org.apache.tools.ant.types.Path mkPath(String name) { if (name == null) { return new org.apache.tools.ant.types.Path(getProject()); } return new org.apache.tools.ant.types.Path(getProject(), name); } private void compile() { Project project = getProject(); setTaskName("compile"); // construct javac task Javac j = new Javac(); j.setProject(project); // See https://github.com/martinpaljak/ant-javacard/pull/96 j.setEncoding("utf-8"); j.setTaskName("compile"); org.apache.tools.ant.types.Path sources = mkPath(null); if (!raw_sources.isEmpty()) { // Per-path includes/excludes via nested elements setTaskName("sources"); Path mergedDir = Misc.makeTemp("sources-" + runIdentifier()); for (JCSources src : raw_sources) { File srcDir = project.resolveFile(src.path); FileSet fs = new FileSet(); fs.setDir(srcDir); fs.setProject(project); if (src.includes != null) { fs.setIncludes(src.includes); } if (src.excludes != null) { fs.setExcludes(src.excludes); } String[] matched = fs.getDirectoryScanner(project).getIncludedFiles(); for (String rel : matched) { Path from = srcDir.toPath().resolve(rel); Path to = mergedDir.resolve(rel); log("using " + from, Project.MSG_INFO); try { Path parent = to.getParent(); if (parent != null) { Files.createDirectories(parent); } Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { throw new BuildException("Failed to copy source file: " + from, e); } } } setTaskName("compile"); sources.append(mkPath(mergedDir.toAbsolutePath().toString())); } else { // Legacy: flat sources/sources2/includes/excludes attributes String pattern = Pattern.quote(File.pathSeparator); String[] sources_paths = sources_path.split(pattern); for (String path : sources_paths) { sources.append(mkPath(path)); } // Old style - second folder if (sources2_path != null) { sources.append(mkPath(sources2_path)); } if (includes != null) { j.setIncludes(includes); } if (excludes != null) { j.setExcludes(excludes); } } j.setSrcdir(sources); // We resolve files to compile based on the sources/includes/excludes parameters, so don't set sourcepath j.setSourcepath(new org.apache.tools.ant.types.Path(project, null)); log("Compiling files from " + sources, Project.MSG_INFO); // determine output directory Path tmp; if (classes_path != null) { // if specified use that tmp = project.resolveFile(classes_path).toPath(); if (!Files.exists(tmp)) { try { Files.createDirectories(tmp); } catch (IOException e) { throw new BuildException("Could not create classes folder " + tmp.toAbsolutePath()); } } } else { // else generate temporary folder tmp = Misc.makeTemp("classes-" + runIdentifier()); classes_path = tmp.toAbsolutePath().toString(); } j.setDestdir(tmp.toFile()); // See "Setting Java Compiler Options" in User Guide j.setDebug(true); j.setDebugLevel("lines,vars,source"); // set the best option supported by jckit String javaVersion = jckit.getVersion().javaVersion(); // Warn in human-readable way if Java not compatible with JC Kit // See https://github.com/martinpaljak/ant-javacard/issues/79 int jdkver = Misc.getCurrentJDKVersion(); if (!jckit.getVersion().jdkVersions().contains(jdkver)) { if (jdkver > 17 && !jckit.getVersion().isOneOf(V320_25_0)) { // JDK 21 can't create 1.7 class files, last version supported by JC kit 3.2 throw new HelpingBuildException("JDK 17 is the latest supported JDK."); } else if (jckit.getVersion().isOneOf(V211, V212, V221, V222) && jdkver > 8) { // JDK 8 is the last version capable of creating 1.2 class files, latest version supported by all 2.x JC kits throw new HelpingBuildException("Use JDK 8 with JavaCard kit v2.x"); } else if (jdkver > 11 && !jckit.getVersion().isOneOf(V310, V320, V320_24_1, V320_25_0)) { // JDK 17+ minimal class file target is 1.7, but need 1.6 throw new HelpingBuildException(String.format("Can't use JDK %d with JavaCard kit %s (use JDK 11)", jdkver, jckit.getVersion())); } else if (jdkver == 8 && jckit.getVersion().isOneOf(V320)) { // 24.1 requires JDK-11 to run (while 24.0 and 25.1 can work with JDK-8, encourage updating) throw new HelpingBuildException(String.format("Should not use JDK %d with JavaCard kit %s (use JDK 11 or 17)", jdkver, jckit.getVersion())); } } j.setTarget(javaVersion); j.setSource(javaVersion); j.setIncludeantruntime(false); j.createCompilerArg().setValue("-Xlint"); j.createCompilerArg().setValue("-Xlint:-options"); j.createCompilerArg().setValue("-Xlint:-serial"); if (jckit.getVersion().isOneOf(V304, V305, V310)) { //-processor com.oracle.javacard.stringproc.StringConstantsProcessor \ // -processorpath "JCDK_HOME/lib/tools.jar;JCDK_HOME/lib/api_classic_annotations.jar" \ j.createCompilerArg().setLine("-processor com.oracle.javacard.stringproc.StringConstantsProcessor"); org.apache.tools.ant.types.Path pcp = new Javac().createClasspath(); for (Path jar : jckit.getCompilerJars()) { pcp.append(mkPath(jar.toString())); } j.createCompilerArg().setLine("-processorpath \"" + pcp.toString() + "\""); j.createCompilerArg().setValue("-Xlint:all,-processing"); } j.setFailonerror(true); j.setFork(true); j.setListfiles(true); // set classpath org.apache.tools.ant.types.Path cp = j.createClasspath(); JavaCardSDK sdk = targetsdk == null ? jckit : targetsdk; for (Path jar : sdk.getApiJars()) { cp.append(mkPath(jar.toString())); } for (JCImport i : raw_imports) { // Support import clauses with only jar or exp values if (i.jar != null) { cp.append(mkPath(i.jar)); } } j.execute(); } private void addKitClasses(Java j) { // classpath to jckit bits org.apache.tools.ant.types.Path cp = j.createClasspath(); for (Path jar : jckit.getToolJars()) { cp.append(mkPath(jar.toString())); } j.setClasspath(cp); } private void convert(Path applet_folder, Set exps) { setTaskName("convert"); // construct java task Java j = new Java(this); j.setTaskName("convert"); // XXX: JC 25.0 does not exit with error on conversion issues. j.setFailonerror(true); j.setFork(true); // add classpath for SDK tools addKitClasses(j); // set class depending on SDK if (jckit.getVersion().equalOrNewer(V301)) { j.setClassname("com.sun.javacard.converter.Main"); // Don't create java0.log.0 files in home folder // As a Java process is executed, we need to store it in a config file if (loghack) { Environment.Variable jclog = new Environment.Variable(); jclog.setKey("java.util.logging.config.file"); jclog.setValue(_logconf); j.addSysproperty(jclog); } // XXX: See https://community.oracle.com/message/10452555 // This is disabled, because for whatever reason, having jc.home property set, the above logging suppression does not work. // make all shows no need for it on macos either. //Environment.Variable jchome = new Environment.Variable(); //jchome.setKey("jc.home"); //jchome.setValue(jckit.getRoot().toString()); //j.addSysproperty(jchome); } else { j.setClassname("com.sun.javacard.converter.Converter"); } // output path j.createArg().setLine("-d '" + applet_folder + "'"); // classes for conversion j.createArg().setLine("-classdir '" + classes_path + "'"); // construct export path StringJoiner expstringbuilder = new StringJoiner(File.pathSeparator); // Add targetSDK export files or the -target option if (jckit.getVersion().targets().contains(targetsdk.getVersion())) { j.createArg().setLine("-target " + targetsdk.getVersion().toString()); } else { expstringbuilder.add(targetsdk.getExportDir().toString()); } // imports for (Path imp : exps) { expstringbuilder.add(imp.toString()); } if (expstringbuilder.length() > 0) { j.createArg().setLine("-exportpath '" + expstringbuilder + "'"); } // always be a little verbose j.createArg().setLine("-verbose"); j.createArg().setLine("-nobanner"); // simple options if (debug) { j.createArg().setLine("-debug"); } if (!verify && !jckit.getVersion().isOneOf(V211, V212)) { j.createArg().setLine("-noverify"); } if (jckit.getVersion().equalOrNewer(V301)) { j.createArg().setLine("-useproxyclass"); } if (ints) { j.createArg().setLine("-i"); } if (exportmap) { j.createArg().setLine("-exportmap"); } // determine output types String outputs = "CAP"; if (output_exp != null || (raw_applets.size() > 0 && verify)) { outputs += " EXP"; } if (output_jca != null) { outputs += " JCA"; } j.createArg().setLine("-out " + outputs); // define applets for (JCApplet app : raw_applets) { j.createArg().setLine("-applet " + Misc.hexAID(app.aid) + " " + app.klass); } // package properties j.createArg().setLine(String.format("%s %s %s", package_name, Misc.hexAID(package_aid), package_version)); // report the command log("command: " + j.getCommandLine(), Project.MSG_DEBUG); // execute the converter j.execute(); } // Return an identifier that uniquely identifies "this run", so that temporary // subfolder in $ANT_JAVACARD_TMP would be sufficiently scoped to a private int runIdentifier() { return Objects.hashCode(this); } @Override public void execute() { Project project = getProject(); setTaskName("javacard"); // perform checks check(); try { // Compile first if necessary if (sources_path != null || !raw_sources.isEmpty()) { compile(); } // Create temporary folder and add to cleanup Path applet_folder = Misc.makeTemp("applet-" + runIdentifier()); // Construct exportpath Set exps = new TreeSet<>(); // add imports for (JCImport imp : raw_imports) { // Support import clauses with only jar or exp values final Path f; if (imp.exps != null) { f = Paths.get(imp.exps).toAbsolutePath(); } else { try { // Assume exp files in jar f = Misc.makeTemp("imports-" + runIdentifier()); OffCardVerifier.extractExps(project.resolveFile(imp.jar).toPath(), f); } catch (IOException e) { throw new BuildException("Can not extract EXP files from JAR", e); } } exps.add(f); } // perform conversion convert(applet_folder, exps); // Copy results // Last component of the package String ln = Misc.lastName(package_name); // directory of package String pkgPath = package_name.replace(".", File.separator); Path pkgDir = applet_folder.resolve(pkgPath); Path jcsrc = pkgDir.resolve("javacard"); // Interesting paths inside the JC folder Path cap = jcsrc.resolve(ln + ".cap"); Path exp = jcsrc.resolve(ln + ".exp"); Path jca = jcsrc.resolve(ln + ".jca"); // XXX: JC 25.0 does not exit with error on conversion issues, so manually check if (!Files.exists(cap)) { throw new BuildException("CAP file not generated, check conversion for errors!"); } // Verify if (verify) { setTaskName("verify"); OffCardVerifier verifier = OffCardVerifier.withSDK(jckit); // Add current export file exps.add(exp); exps.add(targetsdk.getExportDir()); try { verifier.verify(cap, new ArrayList<>(exps)); log(String.format("Verification of %s passed", cap), Project.MSG_INFO); } catch (VerifierError | IOException e) { throw new BuildException(String.format("Verification of %s failed: %s", cap, e.getMessage())); } } setTaskName("cap"); // Copy resources to final destination try { // check that a CAP file got created if (!Files.exists(cap)) { throw new BuildException("Can not find CAP in " + jcsrc); } // copy CAP file CAPFile capfile = CAPFile.fromBytes(Files.readAllBytes(cap)); // Create output name, if not given. output_cap = capFileName(capfile, output_cap); // resolve output path Path outCap = project.resolveFile(output_cap).toPath(); // strip classes, if asked if (strip) { CAPFile.strip(cap); } // perform the copy Files.copy(cap, outCap, StandardCopyOption.REPLACE_EXISTING); // report destination log("CAP saved to " + outCap, Project.MSG_INFO); // copy EXP file if (output_exp != null) { setTaskName("exp"); // check that an EXP file got created if (!Files.exists(exp)) { throw new BuildException("Can not find EXP in " + jcsrc); } // resolve output directory Path outExp = project.resolveFile(output_exp).toPath(); // determine package directories Path outExpPkg = outExp.resolve(pkgPath); Path outExpPkgJc = outExpPkg.resolve("javacard"); // create directories if (!Files.exists(outExpPkgJc)) { Files.createDirectories(outExpPkgJc); } // perform the copy Path exp_file = outExpPkgJc.resolve(exp.getFileName()); Files.copy(exp, exp_file, StandardCopyOption.REPLACE_EXISTING); // report destination log("EXP saved to " + exp_file, Project.MSG_INFO); // add the export directory to the export path for verification exps.add(outExp); } // copy JCA file if (output_jca != null) { setTaskName("jca"); // check that a JCA file got created if (!Files.exists(jca)) { throw new BuildException("Can not find JCA in " + jcsrc); } // resolve output path outCap = project.resolveFile(output_jca).toPath(); Files.copy(jca, outCap, StandardCopyOption.REPLACE_EXISTING); log("JCA saved to " + outCap.toAbsolutePath(), Project.MSG_INFO); } // create JAR file if (output_jar != null) { setTaskName("jar"); File outJar = project.resolveFile(output_jar); // create a new JAR task Jar jarz = new Jar(); jarz.setProject(project); jarz.setTaskName("jar"); jarz.setDestFile(outJar); // include class files FileSet jarcls = new FileSet(); jarcls.setDir(project.resolveFile(classes_path)); jarz.add(jarcls); // include conversion output FileSet jarout = new FileSet(); jarout.setDir(applet_folder.toFile()); jarz.add(jarout); // create the JAR jarz.execute(); log("JAR saved to " + outJar.getAbsolutePath(), Project.MSG_INFO); } } catch (IOException e) { e.printStackTrace(); throw new BuildException("Can not copy output CAP, EXP or JCA", e); } } finally { Misc.cleanTemp(); } } private String capFileName(CAPFile cap, String template) { // Override %n with build-context class name (more reliable than CAP metadata for 2.x) String commonNameOverride = null; if (cap.getAppletAIDs().size() == 1 && !cap.getFlags().contains("exports")) { commonNameOverride = raw_applets.get(0).klass; } return Misc.capFileName(cap, template, commonNameOverride); } } ================================================ FILE: task/src/main/java/pro/javacard/ant/JCImport.java ================================================ // SPDX-FileCopyrightText: 2015 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.ant; // Just for Ant: public class JCImport { String exps = null; String jar = null; public void setExps(String msg) { exps = msg; } public void setJar(String msg) { jar = msg; } } ================================================ FILE: task/src/main/java/pro/javacard/ant/JCSources.java ================================================ // SPDX-FileCopyrightText: 2026 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.ant; // Just for Ant: public class JCSources { String path = null; String includes = null; String excludes = null; public void setPath(String msg) { path = msg; } public void setIncludes(String msg) { includes = msg; } public void setExcludes(String msg) { excludes = msg; } } ================================================ FILE: task/src/main/java/pro/javacard/ant/JavaCard.java ================================================ // SPDX-FileCopyrightText: 2015 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.ant; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import java.util.Arrays; import java.util.Vector; import java.util.stream.IntStream; // ... // This is a wrapper task that can contain one or more subtasks for building capfiles. public final class JavaCard extends Task { private final int[] lts = new int[]{8, 11, 17, 21}; private String master_jckit_path = null; private final Vector packages = new Vector<>(); public void setJCKit(String msg) { master_jckit_path = msg; } public JCCap createCap() { JCCap pkg = new JCCap(master_jckit_path); packages.add(pkg); return pkg; } @Override public void execute() { Thread cleanup = new Thread(() -> { log("Ctrl-C, cleaning up", Project.MSG_INFO); Misc.cleanTemp(); }); Runtime.getRuntime().addShutdownHook(cleanup); String ver = JavaCard.class.getPackage().getImplementationVersion(); log("ant-javacard " + (ver == null ? "development" : ver), Project.MSG_INFO); if (IntStream.of(lts).noneMatch(x -> x == Misc.getCurrentJDKVersion())) { log("Please consider using a LTS JDK version: " + Arrays.toString(lts), Project.MSG_WARN); } try { for (JCCap p : packages) { p.execute(); } } finally { Runtime.getRuntime().removeShutdownHook(cleanup); } } } ================================================ FILE: task/src/main/java/pro/javacard/ant/Misc.java ================================================ // SPDX-FileCopyrightText: 2015 Martin Paljak // SPDX-License-Identifier: MIT package pro.javacard.ant; import pro.javacard.capfile.CAPFile; import pro.javacard.capfile.HexUtils; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.List; import java.util.StringJoiner; final class Misc { static List temporary = new ArrayList<>(); static int getCurrentJDKVersion() { String v = System.getProperty("java.version", "0.0.0"); if (v.startsWith("1.8.")) { v = "8." + v.substring(4); } int dot = v.indexOf("."); return Integer.parseInt(v.substring(0, dot == -1 ? v.length() : dot)); } static String hexAID(byte[] aid) { StringJoiner hexaid = new StringJoiner(":"); for (byte b : aid) { hexaid.add(String.format("0x%02X", b)); } return hexaid.toString(); } // For cleaning up temporary files static void rmminusrf(Path path) { try { Files.walkFileTree(path, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException { if (e == null) { Files.delete(dir); return FileVisitResult.CONTINUE; } else { // directory iteration failed throw e; } } }); } catch (FileNotFoundException | NoSuchFileException e) { // Already gone - do nothing. } catch (IOException e) { throw new RuntimeException(e); } } static byte[] stringToBin(String s) { s = s.toLowerCase().replaceAll(" ", "").replaceAll(":", ""); s = s.replaceAll("0x", "").replaceAll("\n", "").replaceAll("\t", ""); s = s.replaceAll(";", ""); return HexUtils.hex2bin(s); } // foo.bar.Baz -> Baz; Foo -> Foo static String lastName(String fqdn) { String ln = fqdn; if (ln.lastIndexOf(".") != -1) { ln = ln.substring(ln.lastIndexOf(".") + 1); } return ln; } static Path makeTemp(String sub) { try { if (System.getenv("ANT_JAVACARD_TMP") != null) { Path tmp = Paths.get(System.getenv("ANT_JAVACARD_TMP"), sub); // NOTE: would like to make sure that the folder is cleaned, but tmp/imports is shared between // all imports and would result in just final import files to survive. Files.createDirectories(tmp); return tmp; } else { Path p = Files.createTempDirectory("jccpro"); temporary.add(p); return p; } } catch (IOException e) { throw new RuntimeException("Can not make temporary folder", e); } } static String commonName(CAPFile cap) { if (cap.getAppletAIDs().size() == 1 && !cap.getFlags().contains("exports")) { String className = cap.getApplets().values().iterator().next(); if (className != null) { return lastName(className); } } return cap.getPackageName(); } static String capFileName(CAPFile cap, String template) { return capFileName(cap, template, null); } static String capFileName(CAPFile cap, String template, String commonNameOverride) { String commonName = commonNameOverride == null ? commonName(cap) : lastName(commonNameOverride); String hash = HexUtils.bin2hex(cap.getLoadFileDataHash("SHA-256")).toLowerCase(); String name = template; name = name.replace("%H", hash); name = name.replace("%h", hash.substring(0, 8)); name = name.replace("%n", commonName); name = name.replace("%p", cap.getPackageName()); name = name.replace("%a", cap.getPackageAID().toString()); name = name.replace("%v", "v" + cap.getPackageVersion()); name = name.replace("%j", cap.guessJavaCardVersion().orElse("unknown")); name = name.replace("%g", cap.guessGlobalPlatformVersion().orElse("unknown")); name = name.replace("%J", String.format("jdk%d", getCurrentJDKVersion())); return name; } static void cleanTemp() { // Do not clean temporary files if manually set temporary path is set. This is useful for debugging. if (System.getenv("ANT_JAVACARD_TMP") != null) { return; } if (Boolean.parseBoolean(System.getenv().getOrDefault("_ANT_JAVACARD_LITTER", "false"))) { System.err.println("Littering filesystem due to _ANT_JAVACARD_LITTER"); return; } // Clean temporary files. for (Path f : temporary) { if (Files.exists(f)) { rmminusrf(f); } } } } ================================================ FILE: tests-1.8.xml ================================================ ================================================ FILE: tests-11.xml ================================================ ================================================ FILE: tests-17.xml ================================================ ================================================ FILE: tests-21.xml ================================================ ================================================ FILE: tests-fail.xml ================================================ ================================================ FILE: tests.xml ================================================