Showing preview only (238K chars total). Download the full file or copy to clipboard to get everything.
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) <year> <copyright holders>
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.
[](https://github.com/martinpaljak/ant-javacard/releases/latest)
[](https://gist.github.com/martinpaljak/c77d11d671260e24eef6c39123345cae)
[](https://github.com/martinpaljak/ant-javacard/blob/master/LICENSE)
[](https://github.com/martinpaljak/ant-javacard/actions)
[](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
<get src="https://github.com/martinpaljak/ant-javacard/releases/latest/download/ant-javacard.jar" dest="." skipexisting="true"/>
```
* Then load the task with the following in your `build.xml` file:
```xml
<taskdef name="javacard" classname="pro.javacard.ant.JavaCard" classpath="ant-javacard.jar"/>
```
* Now you can create applets within your Ant targets like this:
```xml
<javacard>
<cap jckit="/path/to/jckit_dir" aid="0102030405">
<applet class="myapplet.MyApplet" aid="0102030405060708"/>
</cap>
</javacard>
```
(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
<javacard jckit="/path/to/jckit_dir1">
<cap targetsdk="/path/to/jckit_dir2" aid="0102030405" package="package.name" version="0.1" output="MyApplet.cap" sources="src/myapplet" classes="path/to/classes" export="mylib">
<applet class="myapplet.MyApplet" aid="0102030405060708"/>
<import exps="path/to/exps" jar="/path/to/lib.jar"/>
</cap>
</javacard>
```
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 <capfile>`
- run off-card verifier
- `java -jar ant-javacard.jar <sdk> [<targetsdk>] <capfile> <expfiles>`
### 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
<repositories>
<repository>
<id>javacard-pro</id>
<url>https://mvn.javacard.pro/maven/</url>
</repository>
</repositories>
```
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 <martin@martinpaljak.net>"
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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="dist" name="ant-javacard build">
<!-- Build and load the JavaCard task -->
<!-- shenanigans to get the Ant classpath from maven-antrun-plugin, but also work with plain ant -->
<target name="ant-cp" unless="maven.plugin.classpath">
<path id="ant.compile.classpath"/>
</target>
<target name="maven-cp" if="maven.plugin.classpath" depends="ant-cp">
<path id="ant.compile.classpath" path="${maven.plugin.classpath}"/>
</target>
<target name="jcpro" depends="maven-cp">
<mkdir dir="build"/>
<javac debug="true" destdir="build" includeantruntime="true" target="8" source="8">
<src path="capfile/src/main/java"/>
<src path="task/src/main/java"/>
<classpath>
<path refid="ant.compile.classpath"/>
</classpath>
<compilerarg value="-Xlint:-options"/>
<compilerarg value="-Xlint:all"/>
<exclude name="**/module-info.java"/>
</javac>
<!-- Load the fresh task -->
<path id="task">
<pathelement path="build"/>
</path>
<taskdef name="javacard" classname="pro.javacard.ant.JavaCard" classpathref="task"/>
</target>
<!-- Get commit and timestamp from git -->
<target name="git" description="Get git values">
<exec executable="git" outputproperty="git.revision" failifexecutionfails="true" errorproperty="">
<arg value="describe"/>
<arg value="--tags"/>
<arg value="--always"/>
<arg value="--dirty"/>
</exec>
<exec executable="git" outputproperty="git.timestamp" failifexecutionfails="true" errorproperty="">
<arg value="-c"/>
<arg value="log.showSignature=false"/>
<arg value="log"/>
<arg value="-1"/>
<arg value="--format=%aI"/>
</exec>
<condition property="repository.version" value="${git.revision}" else="unknown">
<and>
<isset property="git.revision"/>
<length string="${git.revision}" trim="yes" length="0" when="greater"/>
</and>
</condition>
<condition property="repository.timestamp" value="${git.timestamp}" else="0">
<and>
<isset property="git.timestamp"/>
<length string="${git.timestamp}" trim="yes" length="0" when="greater"/>
</and>
</condition>
<echo level="info">${repository.version} @ ${repository.timestamp}</echo>
</target>
<!-- Package it into a reproducible JAR -->
<target name="dist" depends="jcpro,git">
<!-- Have the modification timestamp from last git commit -->
<jar destfile="ant-javacard.jar" level="9" basedir="build" modificationtime="${repository.timestamp}">
<manifest>
<!-- It is possible to execute ant-javacard.jar... -->
<attribute name="Main-Class" value="pro.javacard.ant.DummyMain"/>
<!-- Blank out volatile values, so the same jar could be built with slight variations in JDK/ant version -->
<attribute name="Created-By" value="ant-javacard build ${repository.version}"/>
<attribute name="Implementation-Version" value="${repository.version}"/>
<attribute name="Ant-Version" value="irrelevant"/>
</manifest>
</jar>
<!-- Now this JAR can be used in your build.xml by placing the jar to -->
<!-- lib folder and having the following in your target: -->
<!-- <taskdef name="javacard" classname="pro.javacard.ant.JavaCard" classpath="lib/ant-javacard.jar"/> -->
</target>
<!-- Build smoke test applets -->
<import file="tests-${ant.java.version}.xml"/>
<import file="tests-fail.xml"/>
<!-- Cleanup! -->
<target name="clean">
<delete dir="build"/>
<delete>
<fileset dir="." includes="*.cap"/>
</delete>
<delete file="ant-javacard.jar"/>
</target>
</project>
================================================
FILE: capfile/pom.xml
================================================
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.martinpaljak</groupId>
<artifactId>ant-javacard-package</artifactId>
<version>26.03.08-SNAPSHOT</version>
</parent>
<artifactId>capfile</artifactId>
<name>JavaCard SDK and CAP file library</name>
<properties>
<project.build.outputTimestamp>2026-03-08T04:57:03Z</project.build.outputTimestamp>
</properties>
<dependencies>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.12.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.17</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.17</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
================================================
FILE: capfile/spotbugs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter xmlns="https://github.com/spotbugs/filter/3.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd">
<!-- two false positives in Java 11, see https://github.com/spotbugs/spotbugs/issues/756 -->
<Match>
<Bug pattern="RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE"/>
</Match>
<Match>
<Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"/>
</Match>
</FindBugsFilter>
================================================
FILE: capfile/src/main/java/module-info.java
================================================
// SPDX-FileCopyrightText: 2024 Martin Paljak <martin@martinpaljak.net>
// 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 <martin@martinpaljak.net>
// 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 <martin@martinpaljak.net>
// 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<String, byte[]> entries; // All raw ZIP entries
// Parsed content
private final Map<AID, String> applets = new LinkedHashMap<>();
private final List<CAPPackage> 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<Path> 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<byte[]> 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<String, byte[]> 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<String, byte[]> readEntries(ZipInputStream in) throws IOException {
Map<String, byte[]> 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<AID> getAppletAIDs() {
List<AID> 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<String> gpv = guessGlobalPlatformVersion();
Optional<String> 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<AID, String> 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<String, Attributes> 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<String> getFlags() {
return flags2strings(flags);
}
public static List<String> flags2strings(byte flags) {
ArrayList<String> 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<CAPPackage> getImports() {
return Collections.unmodifiableList(imports);
}
public Map<AID, String> 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<String> 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<String> 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<String, String> props = new HashMap<>();
props.put("create", "false");
URI zip_disk = URI.create("jar:" + cap.toUri());
try (FileSystem zipfs = FileSystems.newFileSystem(zip_disk, props)) {
List<Path> 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 <martin@martinpaljak.net>
// 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<String> getName() {
return Optional.ofNullable(name);
}
}
================================================
FILE: capfile/src/main/java/pro/javacard/capfile/HexUtils.java
================================================
// SPDX-FileCopyrightText: 2016 Martin Paljak <martin@martinpaljak.net>
// 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 <martin@martinpaljak.net>
// 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<AID, String> javaCardRegistry = new LinkedHashMap<>();
private static final Map<AID, String> 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<String> 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 <martin@martinpaljak.net>
// 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<Integer, int[]> pkgEntries = new HashMap<>(); // index -> [flags, nameIndex, minor, major]
Map<Integer, byte[]> 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 <martin@martinpaljak.net>
// 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<JavaCardSDK> 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<Path> apiJars = getApiJars(version);
List<Path> compilerJars = getCompilerJars(version);
List<Path> 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<Path> apiJars;
private final List<Path> toolJars;
private final List<Path> compilerJars;
private JavaCardSDK(Path root, SDKVersion version, Path exportDir, List<Path> apiJars, List<Path> toolJars, List<Path> 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<Path> getApiJars() {
return Collections.unmodifiableList(apiJars);
}
public List<Path> getCompilerJars() {
return Collections.unmodifiableList(compilerJars);
}
public List<Path> 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<Path> 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<URLClassLoader>() {
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<Path> getAllExportDirs(SDKVersion version) {
ArrayList<Path> dirs = new ArrayList<Path>();
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<Path> getApiJars(SDKVersion version) {
List<Path> 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<Path> getToolJars(SDKVersion version) {
List<Path> 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<Path> getCompilerJars(SDKVersion version) {
List<Path> 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 <martin@martinpaljak.net>
// 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<File> exps) throws VerifierError, IOException {
List<Path> 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<Path> 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<Path> 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<Path> 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<File> 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<Path> extractExps(Path jarfilePath, Path out) throws IOException {
List<Path> exps = new ArrayList<>();
try (JarFile jarfile = new JarFile(jarfilePath.toFile())) {
Enumeration<JarEntry> 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 <martin@martinpaljak.net>
// 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<Integer> jdks;
final Set<SDKVersion> targets;
SDKVersion(String v, String classfile, Collection<SDKVersion> targets, List<Integer> 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<SDKVersion> 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<Integer> jdkVersions() {
return Collections.unmodifiableSet(jdks);
}
public static Optional<SDKVersion> 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 <martin@martinpaljak.net>
// 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 <martin@martinpaljak.net>
// 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 <martin@martinpaljak.net>
// 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<Path> dirs = Files.list(sdksRoot())) {
List<Path> sdkDirs = dirs.filter(Files::isDirectory).sorted().collect(Collectors.toList());
for (Path dir : sdkDirs) {
Optional<JavaCardSDK> sdk = JavaCardSDK.detectSDK(dir);
Assert.assertTrue(sdk.isPresent(), "Failed to detect SDK in " + dir);
List<Path> exportDirs = JavaCardSDK.getAllExportDirs(sdk.get().getVersion());
ArrayList<String> jarPrefixes = new ArrayList<String>();
for (Path exportDir : exportDirs) {
Path fsDir = dir.resolve(exportDir);
if (Files.isDirectory(fsDir)) {
try (Stream<Path> walk = Files.walk(fsDir)) {
List<Path> 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<? extends ZipEntry> 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 <martin@martinpaljak.net>
// 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<Path> 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." name="ant-javacard failed tests">
<include file="kits.xml"/>
<taskdef name="javacard" classname="pro.javacard.ant.JavaCard" classpath="ant-javacard.jar"/>
<!-- Different SDK/JDK incompatibilities -->
<target name="latestkit">
<javacard>
<cap jckit="${JC310}" sources="src/testapplets/integer" targetsdk="${JC222}">
<applet class="testapplets.integer.EmptyInt" aid="0102030405060708"/>
</cap>
</javacard>
</target>
<target name="newjdk">
<javacard>
<cap jckit="${JC304}" sources="src/testapplets/integer" targetsdk="${JC222}">
<applet class="testapplets.integer.EmptyInt" aid="0102030405060708"/>
</cap>
</javacard>
</target>
<target name="oldkit">
<javacard>
<cap jckit="${JC222}" sources="src/testapplets/integer" targetsdk="${JC221}">
<applet class="testapplets.integer.EmptyInt" aid="0102030405060708"/>
</cap>
</javacard>
</target>
<target name="noexport">
<javacard>
<cap jckit="${JC310}" sources="src/testapplets/integer" targetsdk="3.0.4" ints="true">
<applet class="testapplets.integer.EmptyInt" aid="0102030405060708"/>
</cap>
</javacard>
</target>
<!-- Library -->
<target name="test-library">
<javacard jckit="${JC305}">
<cap targetsdk="${JC304}" sources="src/testapplets/library" package="testapplets.library"
aid="01020304050607" export="testlib" version="0.1"/>
</javacard>
</target>
<target name="test-library-user" depends="test-library">
<javacard>
<cap jckit="${JC310}" sources="src/testapplets/libraryuser">
<applet class="testapplets.libraryuser.LibraryUser" aid="0102030405060708"/>
<import jar="testlib/library.jar"/>
</cap>
</javacard>
</target>
</project>
================================================
FILE: kits.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." name="kit defs">
<!-- Some handy shorthands -->
<property name="JC211" value="sdks/jc211_kit"/>
<property name="JC212" value="sdks/jc212_kit"/>
<property name="JC221" value="sdks/jc221_kit"/>
<property name="JC222" value="sdks/jc222_kit"/>
<property name="JC303" value="sdks/jc303_kit"/>
<property name="JC304" value="sdks/jc304_kit"/>
<property name="JC305_1" value="sdks/jc305u1_kit"/>
<property name="JC305_2" value="sdks/jc305u2_kit"/>
<property name="JC305_3" value="sdks/jc305u3_kit"/>
<property name="JC305" value="sdks/jc305u4_kit"/>
<property name="JC310" value="sdks/jc310r20210706_kit"/>
<property name="JC320" value="sdks/jc320v24.0_kit"/>
<property name="JC320_1" value="sdks/jc320v24.1_kit"/>
<property name="JC320_2" value="sdks/jc320v25.0_kit"/>
<property name="JC320_3" value="sdks/jc320v25.1_kit"/>
</project>
================================================
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
================================================
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<groupId>com.github.martinpaljak</groupId>
<artifactId>ant-javacard-package</artifactId>
<version>26.03.08-SNAPSHOT</version>
<description>Easy to use Ant task for building JavaCard applets</description>
<name>ant-javacard package</name>
<url>https://github.com/martinpaljak/ant-javacard</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- GH Actions fails sometimes on first try -->
<maven.wagon.http.retryHandler.count>2</maven.wagon.http.retryHandler.count>
<project.build.outputTimestamp>2026-03-08T04:57:03Z</project.build.outputTimestamp>
</properties>
<repositories>
<repository>
<id>javacard-pro</id>
<name>javacard.pro</name>
<url>https://mvn.javacard.pro/maven/</url>
</repository>
<repository>
<id>javacard-pro-snapshots</id>
<url>https://mvn.javacard.pro/maven/SNAPSHOTS/</url>
</repository>
</repositories>
<!-- Default is publishing to private Maven repo on javacard.pro -->
<distributionManagement>
<repository>
<id>javacard</id>
<url>scpexe://mvn@mvn.javacard.pro/home/mvn/maven/</url>
</repository>
<snapshotRepository>
<id>javacard</id>
<url>scpexe://mvn@mvn.javacard.pro/home/mvn/maven/SNAPSHOTS</url>
</snapshotRepository>
</distributionManagement>
<modules>
<module>capfile</module>
<module>task</module>
</modules>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.openrewrite.maven</groupId>
<artifactId>rewrite-maven-plugin</artifactId>
<version>6.37.0</version>
<configuration>
<runPerSubmodule>true</runPerSubmodule>
<activeRecipes>
<!-- Braces everywhere -->
<recipe>org.openrewrite.staticanalysis.NeedBraces</recipe>
<!-- Standard modifier ordering -->
<recipe>org.openrewrite.staticanalysis.ModifierOrder</recipe>
<!-- Final everywhere -->
<recipe>org.openrewrite.staticanalysis.FinalizePrivateFields</recipe>
</activeRecipes>
</configuration>
<dependencies>
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-static-analysis</artifactId>
<version>2.33.1</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.9.8.3</version>
<configuration>
<excludeFilterFile>spotbugs.xml</excludeFilterFile>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.14</version>
</plugin>
</plugins>
</pluginManagement>
<extensions>
<extension>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ssh-external</artifactId>
<version>3.5.3</version>
</extension>
</extensions>
<plugins>
<!-- Minimum Maven version. Not really relevant with wrapper -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.6.2</version>
<executions>
<execution>
<id>enforce-maven-version</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireJavaVersion>
<version>[11,)</version>
<message>Project requires JDK 11+</message>
</requireJavaVersion>
<requireMavenVersion>
<version>[3.9.15,)</version>
</requireMavenVersion>
<requirePluginVersions/>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<!-- Compiler -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.15.0</version>
<executions>
<execution>
<id>default-compile</id>
<configuration>
<!-- compile everything to ensure module-info contains right entries -->
<release>11</release>
<compilerArgs combine.children="append">
<arg>-Werror</arg>
<arg>-Xlint:all</arg>
<arg>-Xlint:-options</arg>
</compilerArgs>
</configuration>
</execution>
<execution>
<!-- Run tests with 11 -->
<id>default-testCompile</id>
<configuration>
<!-- compile everything to ensure module-info contains right entries -->
<release>11</release>
</configuration>
</execution>
<execution>
<id>base-compile</id>
<goals>
<goal>compile</goal>
</goals>
<!-- recompile everything for 1.8 except the module-info.java -->
<configuration>
<source>8</source>
<target>8</target>
<excludes>
<exclude>module-info.java</exclude>
</excludes>
<compilerArgs combine.children="append">
<arg>-Xlint:-options</arg>
</compilerArgs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.2.0</version>
<inherited>false</inherited>
<executions>
<execution>
<id>ant-dist</id>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<!-- Pass the plugin classpath to antrun, which is normally not there -->
<property name="maven.plugin.classpath" refid="maven.plugin.classpath"/>
<ant antfile="build.xml" target="dist" inheritAll="true"/>
</target>
</configuration>
</execution>
</executions>
</plugin>
<!-- Version fixes -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.5.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>3.1.4</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.1.4</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.21.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.5</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.5.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.5.0</version>
</plugin>
</plugins>
</build>
<!-- Metadata -->
<licenses>
<license>
<name>MIT</name>
<url>https://github.com/martinpaljak/ant-javacard/blob/master/LICENSE</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<url>https://github.com/martinpaljak/ant-javacard</url>
</scm>
<profiles>
<profile>
<id>check</id>
<build>
<plugins>
<plugin>
<groupId>org.openrewrite.maven</groupId>
<artifactId>rewrite-maven-plugin</artifactId>
<executions>
<execution>
<phase>process-sources</phase>
<goals>
<goal>dryRun</goal>
</goals>
<configuration>
<failOnDryRunResults>true</failOnDryRunResults>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>fixup</id>
<build>
<plugins>
<plugin>
<groupId>org.openrewrite.maven</groupId>
<artifactId>rewrite-maven-plugin</artifactId>
<executions>
<execution>
<phase>process-sources</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
================================================
FILE: spotbugs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter xmlns="https://github.com/spotbugs/filter/3.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd">
</FindBugsFilter>
================================================
FILE: src/testapplets/empty/Empty.java
================================================
// SPDX-FileCopyrightText: 2024 Martin Paljak <martin@martinpaljak.net>
// 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 <martin@martinpaljak.net>
// 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 <martin@martinpaljak.net>
// 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 <martin@martinpaljak.net>
// 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 <martin@martinpaljak.net>
// 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 <martin@martinpaljak.net>
// 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 <martin@martinpaljak.net>
// 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 <martin@martinpaljak.net>
// 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
================================================
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Not using metacard, as this is meant to be compilable with Java 1.8 as well -->
<parent>
<groupId>com.github.martinpaljak</groupId>
<artifactId>ant-javacard-package</artifactId>
<version>26.03.08-SNAPSHOT</version>
</parent>
<artifactId>ant-javacard</artifactId>
<description>Easy to use Ant task for building JavaCard applets</description>
<name>ant-javacard task</name>
<dependencies>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.10.17</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.github.martinpaljak</groupId>
<artifactId>capfile</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.12.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.17</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>pro.javacard.ant.DummyMain</mainClass>
</manifest>
<manifestEntries>
<Implementation-Version>${project.version}</Implementation-Version>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
================================================
FILE: task/src/main/java/pro/javacard/ant/DummyMain.java
================================================
// SPDX-FileCopyrightText: 2015 Martin Paljak <martin@martinpaljak.net>
// 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<String> 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 <capfile>");
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 <capfile>");
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 <capfile>");
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 <capfile|expfile>");
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<File> 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 <martin@martinpaljak.net>
// 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 <martin@martinpaljak.net>
// 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 <martin@martinpaljak.net>
// 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.*;
// <cap ...>...</cap> 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<JCApplet> raw_applets = new ArrayList<>();
private final List<JCImport> raw_imports = new ArrayList<>();
private final List<JCSources> 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 <sources path="" includes="" excludes=""/> elements
public JCSources createSources() {
JCSources src = new JCSources();
raw_sources.add(src);
return src;
}
private Optional<JavaCardSDK> 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<SDKVersion> 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 <sources> 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 <sources> 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 <sources> 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 <sources> 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<Path> 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 <cap/>
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<Path> 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
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
SYMBOL INDEX (231 symbols across 29 files)
FILE: capfile/src/main/java/pro/javacard/capfile/AID.java
class AID (line 8) | public final class AID {
method AID (line 11) | public AID(byte[] bytes) throws IllegalArgumentException {
method AID (line 15) | public AID(String str) throws IllegalArgumentException {
method AID (line 19) | public AID(byte[] bytes, int offset, int length) throws IllegalArgumen...
method fromString (line 26) | public static AID fromString(Object s) {
method getBytes (line 33) | public byte[] getBytes() {
method getLength (line 37) | public int getLength() {
method toString (line 41) | @Override
method hashCode (line 46) | @Override
method equals (line 51) | @Override
FILE: capfile/src/main/java/pro/javacard/capfile/CAPFile.java
class CAPFile (line 35) | public final class CAPFile {
method fromStream (line 52) | public static CAPFile fromStream(InputStream in) throws IOException {
method fromBytes (line 56) | public static CAPFile fromBytes(byte[] bytes) throws IOException {
method fromFile (line 60) | public static CAPFile fromFile(Path path) throws IOException {
method getFile (line 68) | public Optional<Path> getFile() {
method getComponent (line 72) | public byte[] getComponent(String name) {
method getMetaInfEntry (line 77) | public byte[] getMetaInfEntry(String name) {
method getZipComponent (line 81) | public Optional<byte[]> getZipComponent(String name) {
method store (line 85) | public void store(OutputStream to) throws IOException {
method CAPFile (line 96) | protected CAPFile(InputStream in) throws IOException {
method readEntries (line 191) | private static Map<String, byte[]> readEntries(ZipInputStream in) thro...
method getPackageAID (line 207) | public AID getPackageAID() {
method getAppletAIDs (line 211) | public List<AID> getAppletAIDs() {
method getPackageVersion (line 217) | public String getPackageVersion() {
method getPackageName (line 221) | public String getPackageName() {
method getCode (line 225) | public byte[] getCode() {
method getCode (line 229) | @Deprecated
method _getCode (line 234) | byte[] _getCode(boolean includeDebug) {
method getLoadFileDataHash (line 253) | public byte[] getLoadFileDataHash(String hash) {
method getLoadFileDataHash (line 261) | @Deprecated
method dump (line 270) | public void dump(PrintStream out) {
method getFlags (line 310) | public List<String> getFlags() {
method flags2strings (line 314) | public static List<String> flags2strings(byte flags) {
method getImports (line 329) | public List<CAPPackage> getImports() {
method getApplets (line 333) | public Map<AID, String> getApplets() {
method guessJavaCardVersion (line 344) | public Optional<String> guessJavaCardVersion() {
method guessGlobalPlatformVersion (line 407) | public Optional<String> guessGlobalPlatformVersion() {
method pkg2jcdir (line 428) | private static String pkg2jcdir(String pkgname) {
method jcdir2pkg (line 432) | private static String jcdir2pkg(String jcdir) {
method uncheckedDelete (line 436) | public static void uncheckedDelete(Path p) throws UncheckedIOException {
method strip (line 445) | public static void strip(Path cap) throws IOException {
FILE: capfile/src/main/java/pro/javacard/capfile/CAPPackage.java
class CAPPackage (line 9) | public final class CAPPackage {
method CAPPackage (line 15) | public CAPPackage(AID aid, int major, int minor) {
method CAPPackage (line 19) | public CAPPackage(AID aid, int major, int minor, String name) {
method equals (line 26) | @Override
method hashCode (line 35) | @Override
method toString (line 40) | @Override
method getVersionString (line 45) | public String getVersionString() {
method getAid (line 49) | public AID getAid() {
method getMinor (line 53) | public int getMinor() {
method getMajor (line 57) | public int getMajor() {
method getName (line 61) | public Optional<String> getName() {
FILE: capfile/src/main/java/pro/javacard/capfile/HexUtils.java
class HexUtils (line 6) | public class HexUtils {
method HexUtils (line 7) | private HexUtils() {}
method encodeHexString_imp (line 12) | public static String encodeHexString_imp(final byte[] data) {
method decodeHexString_imp (line 24) | public static byte[] decodeHexString_imp(String str) {
method hex2bin (line 49) | public static byte[] hex2bin(final String hex) {
method bin2hex (line 53) | public static String bin2hex(final byte[] bin) {
method stringToBin (line 57) | public static byte[] stringToBin(String s) {
FILE: capfile/src/main/java/pro/javacard/capfile/WellKnownAID.java
class WellKnownAID (line 17) | public final class WellKnownAID {
method WellKnownAID (line 22) | private WellKnownAID() {}
method load (line 89) | public static void load(InputStream in) {
method load (line 104) | public static void load(Path p) {
method getJavaCardName (line 115) | public static String getJavaCardName(AID aid) {
method getName (line 119) | public static Optional<String> getName(AID aid) {
FILE: capfile/src/main/java/pro/javacard/sdk/ExportFileHelper.java
class ExportFileHelper (line 17) | public final class ExportFileHelper {
type ExportFileVersion (line 28) | public enum ExportFileVersion {
class PackageInfo (line 34) | public static final class PackageInfo {
method PackageInfo (line 42) | PackageInfo(ExportFileVersion version, String name, byte[] aid,
method getVersion (line 52) | public ExportFileVersion getVersion() {
method getName (line 56) | public String getName() {
method getAid (line 60) | public byte[] getAid() {
method getMajor (line 64) | public int getMajor() {
method getMinor (line 68) | public int getMinor() {
method getPackageVersion (line 72) | public String getPackageVersion() {
method isLibrary (line 76) | public boolean isLibrary() {
method toString (line 80) | @Override
method ExportFileHelper (line 86) | private ExportFileHelper() {
method parsePackage (line 89) | public static PackageInfo parsePackage(Path path) throws IOException {
method parsePackage (line 95) | public static PackageInfo parsePackage(InputStream in) throws IOExcept...
method parseFileVersion (line 190) | private static ExportFileVersion parseFileVersion(int minor) {
FILE: capfile/src/main/java/pro/javacard/sdk/JavaCardSDK.java
class JavaCardSDK (line 20) | public final class JavaCardSDK {
method detectSDK (line 22) | public static Optional<JavaCardSDK> detectSDK(Path path) {
method detectSDKVersion (line 43) | private static SDKVersion detectSDKVersion(Path root) {
method JavaCardSDK (line 107) | private JavaCardSDK(Path root, SDKVersion version, Path exportDir, Lis...
method getRoot (line 117) | public Path getRoot() {
method getVersion (line 121) | public SDKVersion getVersion() {
method getApiJars (line 125) | public List<Path> getApiJars() {
method getCompilerJars (line 129) | public List<Path> getCompilerJars() {
method getToolJars (line 133) | public List<Path> getToolJars() {
method getExportDir (line 137) | public Path getExportDir() {
method target (line 142) | public JavaCardSDK target(SDKVersion targetVersion) {
method getClassLoader (line 155) | @SuppressWarnings("removal") // AccessController
method getRelease (line 172) | public String getRelease() {
method getAllExportDirs (line 205) | public static List<Path> getAllExportDirs(SDKVersion version) {
method getExportDir (line 214) | public static Path getExportDir(SDKVersion version) {
method getApiJars (line 229) | public static List<Path> getApiJars(SDKVersion version) {
method getToolJars (line 265) | public static List<Path> getToolJars(SDKVersion version) {
method getCompilerJars (line 279) | public static List<Path> getCompilerJars(SDKVersion version) {
method equals (line 291) | @Override
method hashCode (line 300) | @Override
FILE: capfile/src/main/java/pro/javacard/sdk/OffCardVerifier.java
class OffCardVerifier (line 23) | public final class OffCardVerifier {
method withSDK (line 26) | public static OffCardVerifier withSDK(JavaCardSDK sdk) {
method OffCardVerifier (line 34) | private OffCardVerifier(JavaCardSDK sdk) {
method verifyAgainst (line 39) | public void verifyAgainst(File f, JavaCardSDK target, Vector<File> exp...
method verifyAgainst (line 45) | public void verifyAgainst(Path f, JavaCardSDK target, List<Path> exps)...
method verify (line 60) | public void verify(Path f, List<Path> exps) throws VerifierError, IOEx...
method rmminusrf (line 114) | private static void rmminusrf(Path path) {
method under (line 124) | private static Path under(Path out, String name) {
method extractExps (line 133) | public static List<Path> extractExps(Path jarfilePath, Path out) throw...
FILE: capfile/src/main/java/pro/javacard/sdk/SDKVersion.java
type SDKVersion (line 8) | public enum SDKVersion {
method SDKVersion (line 30) | SDKVersion(String v, String classfile, Collection<SDKVersion> targets,...
method toString (line 37) | @Override
method targets (line 42) | public Set<SDKVersion> targets() {
method isOneOf (line 46) | public boolean isOneOf(SDKVersion... versions) {
method javaVersion (line 55) | public String javaVersion() {
method jdkVersions (line 59) | public Set<Integer> jdkVersions() {
method fromVersion (line 63) | public static Optional<SDKVersion> fromVersion(String versionString) {
method equalOrNewer (line 67) | public boolean equalOrNewer(SDKVersion other) {
FILE: capfile/src/main/java/pro/javacard/sdk/VerifierError.java
class VerifierError (line 6) | public class VerifierError extends Exception {
method VerifierError (line 9) | public VerifierError(String message, Throwable cause) {
method VerifierError (line 13) | public VerifierError(String message) {
FILE: capfile/src/test/java/pro/javacard/capfile/TestWellKnownAID.java
class TestWellKnownAID (line 12) | public class TestWellKnownAID {
method testInternalList (line 14) | @Test
FILE: capfile/src/test/java/pro/javacard/sdk/TestExportFiles.java
class TestExportFiles (line 24) | public class TestExportFiles {
method sdksRoot (line 26) | static Path sdksRoot() {
method testParseAllExportFiles (line 30) | @Test
method testBadMagic (line 153) | @Test(expectedExceptions = IllegalArgumentException.class)
method testBadMajorVersion (line 158) | @Test(expectedExceptions = IllegalArgumentException.class)
method testBadMinorVersion (line 163) | @Test(expectedExceptions = IllegalArgumentException.class)
FILE: capfile/src/test/java/pro/javacard/sdk/TestSDKs.java
class TestSDKs (line 15) | public class TestSDKs {
method sdksRoot (line 17) | static Path sdksRoot() {
method sdk (line 21) | static JavaCardSDK sdk(String name) {
method testDetection (line 25) | @Test
FILE: src/testapplets/empty/Empty.java
class Empty (line 12) | public class Empty extends Applet {
method Empty (line 14) | private Empty(byte[] parameters, short offset, byte length) {
method install (line 18) | public static void install(byte[] parameters, short offset, byte lengt...
method process (line 22) | public void process(APDU apdu) throws ISOException {
FILE: src/testapplets/fail/Fail.java
class Fail (line 12) | public class Fail extends Applet {
method Fail (line 14) | private Fail(byte[] parameters, short offset, byte length) {
method install (line 18) | public static void install(byte[] parameters, short offset, byte lengt...
method process (line 22) | public void process(APDU apdu) throws ISOException {
FILE: src/testapplets/integer/EmptyInt.java
class EmptyInt (line 10) | public class EmptyInt extends Applet {
method EmptyInt (line 12) | private EmptyInt(byte[] parameters, short offset, byte length) {
method install (line 17) | public static void install(byte[] parameters, short offset, byte lengt...
method process (line 21) | public void process(APDU arg0) throws ISOException {
FILE: src/testapplets/library/SomeLibrary.java
class SomeLibrary (line 9) | public class SomeLibrary {
method booleantest (line 13) | public static short booleantest(boolean b) {
method getRandom (line 17) | public static RandomData getRandom() {
FILE: src/testapplets/libraryuser/LibraryUser.java
class LibraryUser (line 11) | public class LibraryUser extends Applet {
method LibraryUser (line 15) | private LibraryUser(boolean bvalue) {
method install (line 19) | public static void install(byte[] parameters, short offset, byte lengt...
method process (line 23) | public void process(APDU arg0) throws ISOException {
FILE: src/testapplets/multiapp/First.java
class First (line 11) | public class First extends Applet {
method First (line 13) | private First(byte[] parameters, short offset, byte length) {
method install (line 17) | public static void install(byte[] parameters, short offset, byte lengt...
method process (line 21) | public void process(APDU arg0) throws ISOException {
FILE: src/testapplets/multiapp/Second.java
class Second (line 11) | public class Second extends Applet {
method Second (line 13) | private Second(byte[] parameters, short offset, byte length) {
method install (line 17) | public static void install(byte[] parameters, short offset, byte lengt...
method process (line 21) | public void process(APDU arg0) throws ISOException {
FILE: src/testapplets/stringdefs/Empty.java
class Empty (line 13) | @StringPool(value = {
method Empty (line 20) | private Empty(byte[] parameters, short offset, byte length) {
method install (line 24) | public static void install(byte[] parameters, short offset, byte lengt...
method process (line 28) | public void process(APDU arg0) throws ISOException {
FILE: task/src/main/java/pro/javacard/ant/DummyMain.java
class DummyMain (line 26) | public final class DummyMain {
method rename (line 28) | static Path rename(Path path, String template) throws IOException {
method runcycle (line 40) | static int runcycle(String[] argv) throws IOException {
method main (line 156) | public static void main(String[] argv) {
FILE: task/src/main/java/pro/javacard/ant/HelpingBuildException.java
class HelpingBuildException (line 8) | public class HelpingBuildException extends BuildException {
method HelpingBuildException (line 11) | public HelpingBuildException(String msg) {
FILE: task/src/main/java/pro/javacard/ant/JCApplet.java
class JCApplet (line 9) | public class JCApplet {
method JCApplet (line 13) | public JCApplet() {
method setClass (line 16) | public void setClass(String msg) {
method setAID (line 20) | public void setAID(String msg) {
FILE: task/src/main/java/pro/javacard/ant/JCCap.java
class JCCap (line 32) | public class JCCap extends Task {
method JCCap (line 87) | public JCCap(String master_jckit_path) {
method setJCKit (line 91) | public void setJCKit(String msg) {
method setOutput (line 95) | public void setOutput(String msg) {
method setExport (line 99) | public void setExport(String msg) {
method setJar (line 103) | public void setJar(String msg) {
method setJca (line 107) | public void setJca(String msg) {
method setPackage (line 111) | public void setPackage(String msg) {
method setClasses (line 115) | public void setClasses(String msg) {
method setVersion (line 119) | public void setVersion(String msg) {
method setSources (line 123) | public void setSources(String arg) {
method setSources2 (line 127) | public void setSources2(String arg) {
method setIncludes (line 131) | public void setIncludes(String arg) {
method setExcludes (line 135) | public void setExcludes(String arg) {
method setVerify (line 139) | public void setVerify(boolean arg) {
method setDebug (line 143) | public void setDebug(boolean arg) {
method setStrip (line 147) | public void setStrip(boolean arg) {
method setInts (line 151) | public void setInts(boolean arg) {
method setExportmap (line 155) | public void setExportmap(boolean arg) {
method setTargetsdk (line 159) | public void setTargetsdk(String arg) {
method setAID (line 163) | public void setAID(String msg) {
method createApplet (line 176) | public JCApplet createApplet() {
method createImport (line 183) | public JCImport createImport() {
method createJimport (line 190) | public JCImport createJimport() {
method createSources (line 195) | public JCSources createSources() {
method findSDK (line 201) | private Optional<JavaCardSDK> findSDK() {
method check (line 225) | private void check() {
method mkPath (line 402) | private org.apache.tools.ant.types.Path mkPath(String name) {
method compile (line 409) | private void compile() {
method addKitClasses (line 565) | private void addKitClasses(Java j) {
method convert (line 574) | private void convert(Path applet_folder, Set<Path> exps) {
method runIdentifier (line 682) | private int runIdentifier() {
method execute (line 686) | @Override
method capFileName (line 855) | private String capFileName(CAPFile cap, String template) {
FILE: task/src/main/java/pro/javacard/ant/JCImport.java
class JCImport (line 7) | public class JCImport {
method setExps (line 11) | public void setExps(String msg) {
method setJar (line 15) | public void setJar(String msg) {
FILE: task/src/main/java/pro/javacard/ant/JCSources.java
class JCSources (line 7) | public class JCSources {
method setPath (line 12) | public void setPath(String msg) {
method setIncludes (line 16) | public void setIncludes(String msg) {
method setExcludes (line 20) | public void setExcludes(String msg) {
FILE: task/src/main/java/pro/javacard/ant/JavaCard.java
class JavaCard (line 15) | public final class JavaCard extends Task {
method setJCKit (line 21) | public void setJCKit(String msg) {
method createCap (line 25) | public JCCap createCap() {
method execute (line 31) | @Override
FILE: task/src/main/java/pro/javacard/ant/Misc.java
class Misc (line 17) | final class Misc {
method getCurrentJDKVersion (line 21) | static int getCurrentJDKVersion() {
method hexAID (line 30) | static String hexAID(byte[] aid) {
method rmminusrf (line 39) | static void rmminusrf(Path path) {
method stringToBin (line 68) | static byte[] stringToBin(String s) {
method lastName (line 76) | static String lastName(String fqdn) {
method makeTemp (line 84) | static Path makeTemp(String sub) {
method commonName (line 102) | static String commonName(CAPFile cap) {
method capFileName (line 112) | static String capFileName(CAPFile cap, String template) {
method capFileName (line 116) | static String capFileName(CAPFile cap, String template, String commonN...
method cleanTemp (line 133) | static void cleanTemp() {
Condensed preview — 57 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (239K chars).
[
{
"path": ".github/workflows/robot.yml",
"chars": 1979,
"preview": "on:\n push:\n tags:\n - 'v*'\n branches:\n - master\n - next\n pull_request:\n branches:\n - maste"
},
{
"path": ".gitignore",
"chars": 152,
"preview": "**/target\n*.cap\n*.class\n*.iml\n*~\n/*.iml\n/*.jar\n/*.jca\n/.idea\n/.mvn/wrapper/maven-wrapper.jar\n/target\n/build\n/testlib\nhs_"
},
{
"path": ".gitmodules",
"chars": 67,
"preview": "[submodule \"sdks\"]\n\tpath = sdks\n\turl = ../oracle_javacard_sdks.git\n"
},
{
"path": ".mvn/wrapper/maven-wrapper.properties",
"chars": 280,
"preview": "wrapperVersion=3.3.4\ndistributionType=script\ndistributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apach"
},
{
"path": "LICENSE",
"chars": 1086,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2015-2024 Martin Paljak\n\nPermission is hereby granted, free of charge, to any perso"
},
{
"path": "LICENSES/Apache-2.0.txt",
"chars": 10280,
"preview": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AN"
},
{
"path": "LICENSES/MIT.txt",
"chars": 1078,
"preview": "MIT License\n\nCopyright (c) <year> <copyright holders>\n\nPermission is hereby granted, free of charge, to any person obtai"
},
{
"path": "Makefile",
"chars": 1152,
"preview": "TZ = UTC # same as Github\nexport TZ\nSHELL := /bin/bash\nJDK := zulu\nJAVA8 := /Library/Java/JavaVirtualMachines/$(JDK)-8.j"
},
{
"path": "README.md",
"chars": 14369,
"preview": "# Building JavaCard applet CAP files with Ant\n\n> Easy to use [Apache Ant](https://ant.apache.org/) task for building Jav"
},
{
"path": "REUSE.toml",
"chars": 985,
"preview": "version = 1\n\n# Project-owned configuration, build, and documentation files.\n# These are not Java source and do not carry"
},
{
"path": "build.xml",
"chars": 4116,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project basedir=\".\" default=\"dist\" name=\"ant-javacard build\">\n <!-- Build and"
},
{
"path": "capfile/pom.xml",
"chars": 1317,
"preview": "<?xml version=\"1.0\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-ins"
},
{
"path": "capfile/spotbugs.xml",
"chars": 595,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<FindBugsFilter xmlns=\"https://github.com/spotbugs/filter/3.0.0\" xmlns:xsi=\"http:"
},
{
"path": "capfile/src/main/java/module-info.java",
"chars": 294,
"preview": "// SPDX-FileCopyrightText: 2024 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\n@SuppressWarnin"
},
{
"path": "capfile/src/main/java/pro/javacard/capfile/AID.java",
"chars": 1474,
"preview": "// SPDX-FileCopyrightText: 2018 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "capfile/src/main/java/pro/javacard/capfile/CAPFile.java",
"chars": 17804,
"preview": "// SPDX-FileCopyrightText: 2018 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\n// Loosely base"
},
{
"path": "capfile/src/main/java/pro/javacard/capfile/CAPPackage.java",
"chars": 1490,
"preview": "// SPDX-FileCopyrightText: 2018 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "capfile/src/main/java/pro/javacard/capfile/HexUtils.java",
"chars": 2174,
"preview": "// SPDX-FileCopyrightText: 2016 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "capfile/src/main/java/pro/javacard/capfile/WellKnownAID.java",
"chars": 6080,
"preview": "// SPDX-FileCopyrightText: 2018 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "capfile/src/main/java/pro/javacard/sdk/ExportFileHelper.java",
"chars": 7118,
"preview": "// SPDX-FileCopyrightText: 2024 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "capfile/src/main/java/pro/javacard/sdk/JavaCardSDK.java",
"chars": 11636,
"preview": "// SPDX-FileCopyrightText: 2015 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "capfile/src/main/java/pro/javacard/sdk/OffCardVerifier.java",
"chars": 7556,
"preview": "// SPDX-FileCopyrightText: 2018 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "capfile/src/main/java/pro/javacard/sdk/SDKVersion.java",
"chars": 2422,
"preview": "// SPDX-FileCopyrightText: 2022 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "capfile/src/main/java/pro/javacard/sdk/VerifierError.java",
"chars": 424,
"preview": "// SPDX-FileCopyrightText: 2018 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "capfile/src/main/resources/pro/javacard/capfile/aid_list.properties",
"chars": 920,
"preview": "# from https://www.commoncriteriaportal.org/files/epfiles/anssi-cc-2018_32fr.pdf\nA0000000308000000008DB00FF=com.gemalto."
},
{
"path": "capfile/src/test/java/pro/javacard/capfile/TestWellKnownAID.java",
"chars": 630,
"preview": "// SPDX-FileCopyrightText: 2024 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "capfile/src/test/java/pro/javacard/sdk/TestExportFiles.java",
"chars": 8536,
"preview": "// SPDX-FileCopyrightText: 2024 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "capfile/src/test/java/pro/javacard/sdk/TestSDKs.java",
"chars": 3285,
"preview": "// SPDX-FileCopyrightText: 2024 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "fails.xml",
"chars": 2047,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project basedir=\".\" name=\"ant-javacard failed tests\">\n <include file=\"kits.xm"
},
{
"path": "kits.xml",
"chars": 955,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project basedir=\".\" name=\"kit defs\">\n <!-- Some handy shorthands -->\n <pro"
},
{
"path": "mvnw",
"chars": 11336,
"preview": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Softwa"
},
{
"path": "mvnw.cmd",
"chars": 7903,
"preview": "@REM ----------------------------------------------------------------------------\r\n@REM Licensed to the Apache Software "
},
{
"path": "pom.xml",
"chars": 13393,
"preview": "<?xml version=\"1.0\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-ins"
},
{
"path": "spotbugs.xml",
"chars": 329,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<FindBugsFilter xmlns=\"https://github.com/spotbugs/filter/3.0.0\" xmlns:xsi=\"http:"
},
{
"path": "src/testapplets/empty/Empty.java",
"chars": 989,
"preview": "// SPDX-FileCopyrightText: 2024 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage testapp"
},
{
"path": "src/testapplets/fail/Fail.java",
"chars": 1019,
"preview": "// SPDX-FileCopyrightText: 2024 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage testapp"
},
{
"path": "src/testapplets/integer/EmptyInt.java",
"chars": 626,
"preview": "// SPDX-FileCopyrightText: 2024 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage testapp"
},
{
"path": "src/testapplets/library/SomeLibrary.java",
"chars": 513,
"preview": "// SPDX-FileCopyrightText: 2024 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage testapp"
},
{
"path": "src/testapplets/libraryuser/LibraryUser.java",
"chars": 743,
"preview": "// SPDX-FileCopyrightText: 2024 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage testapp"
},
{
"path": "src/testapplets/multiapp/First.java",
"chars": 678,
"preview": "// SPDX-FileCopyrightText: 2024 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage testapp"
},
{
"path": "src/testapplets/multiapp/Second.java",
"chars": 681,
"preview": "// SPDX-FileCopyrightText: 2024 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage testapp"
},
{
"path": "src/testapplets/stringdefs/Empty.java",
"chars": 894,
"preview": "// SPDX-FileCopyrightText: 2024 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage testapp"
},
{
"path": "task/pom.xml",
"chars": 2303,
"preview": "<?xml version=\"1.0\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-ins"
},
{
"path": "task/src/main/java/pro/javacard/ant/DummyMain.java",
"chars": 7648,
"preview": "// SPDX-FileCopyrightText: 2015 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "task/src/main/java/pro/javacard/ant/HelpingBuildException.java",
"chars": 457,
"preview": "// SPDX-FileCopyrightText: 2015 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "task/src/main/java/pro/javacard/ant/JCApplet.java",
"chars": 773,
"preview": "// SPDX-FileCopyrightText: 2015 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "task/src/main/java/pro/javacard/ant/JCCap.java",
"chars": 34914,
"preview": "// SPDX-FileCopyrightText: 2015 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "task/src/main/java/pro/javacard/ant/JCImport.java",
"chars": 375,
"preview": "// SPDX-FileCopyrightText: 2015 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "task/src/main/java/pro/javacard/ant/JCSources.java",
"chars": 510,
"preview": "// SPDX-FileCopyrightText: 2026 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "task/src/main/java/pro/javacard/ant/JavaCard.java",
"chars": 1647,
"preview": "// SPDX-FileCopyrightText: 2015 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "task/src/main/java/pro/javacard/ant/Misc.java",
"chars": 5401,
"preview": "// SPDX-FileCopyrightText: 2015 Martin Paljak <martin@martinpaljak.net>\n// SPDX-License-Identifier: MIT\n\npackage pro.jav"
},
{
"path": "tests-1.8.xml",
"chars": 6587,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project basedir=\".\" default=\"test\" name=\"ant-javacard tests\">\n <import file=\""
},
{
"path": "tests-11.xml",
"chars": 5332,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project basedir=\".\" default=\"test\" name=\"ant-javacard tests\">\n <import file=\""
},
{
"path": "tests-17.xml",
"chars": 2002,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project basedir=\".\" default=\"test\" name=\"ant-javacard tests\">\n <import file=\""
},
{
"path": "tests-21.xml",
"chars": 1547,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project basedir=\".\" default=\"test\" name=\"ant-javacard tests\">\n <import file=\""
},
{
"path": "tests-fail.xml",
"chars": 557,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project basedir=\".\" default=\"fail\" name=\"ant-javacard failing tests\">\n <impor"
},
{
"path": "tests.xml",
"chars": 5075,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project basedir=\".\" default=\"test\" name=\"ant-javacard tests\">\n <include file="
}
]
About this extraction
This page contains the full source code of the martinpaljak/ant-javacard GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 57 files (221.2 KB), approximately 56.3k tokens, and a symbol index with 231 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.