From 7a3ede6617172710b220615d4a3c3a61571fab06 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Thu, 8 Feb 2024 13:45:10 +0100 Subject: [PATCH 01/13] feat: add initial interfaces --- .gitattributes | 50 +++++ .gitignore | 44 ++++ CHANGELOG.md | 9 + CODE_OF_CONDUCT.md | 93 ++++++++ CONTRIBUTING.md | 43 ++++ Jenkinsfile | 83 +++++++ LICENSE | 21 ++ NOTICE.md | 34 +++ PUBLISHERS.yml | 18 ++ README.md | 25 ++- SECURITY.md | 9 + build.gradle.kts | 59 +++++ gradle.properties | 15 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 185 ++++++++++++++++ gradlew.bat | 89 ++++++++ scripts/check_version.sh | 12 + scripts/prepare_javadoc.sh | 52 +++++ settings.gradle.kts | 1 + .../CalypsoCertificateApiFactory.java | 64 ++++++ .../CalypsoCertificateApiProperties.java | 28 +++ .../certificate/ca/CaCertificateManager.java | 25 +++ .../certificate/ca/CaCertificateSettings.java | 18 ++ .../ca/CaCertificateSettingsV1.java | 206 ++++++++++++++++++ .../calypso/certificate/ca/package-info.java | 6 + .../ca/spi/CaCertificateSignerSpi.java | 54 +++++ .../certificate/ca/spi/package-info.java | 6 + .../card/CardCertificateManager.java | 34 +++ .../card/CardCertificateSettings.java | 18 ++ .../card/CardCertificateSettingsV1.java | 133 +++++++++++ .../certificate/card/package-info.java | 6 + .../card/spi/CardCertificateSignerSpi.java | 54 +++++ .../certificate/card/spi/package-info.java | 6 + .../calypso/certificate/package-info.java | 6 + src/main/javadoc/overview.html | 15 ++ .../CalypsoCertificateApiPropertiesTest.java | 42 ++++ 37 files changed, 1566 insertions(+), 2 deletions(-) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 Jenkinsfile create mode 100644 LICENSE create mode 100644 NOTICE.md create mode 100644 PUBLISHERS.yml create mode 100644 SECURITY.md create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 scripts/check_version.sh create mode 100644 scripts/prepare_javadoc.sh create mode 100644 settings.gradle.kts create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiProperties.java create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateManager.java create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateSettings.java create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateSettingsV1.java create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/ca/package-info.java create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/CaCertificateSignerSpi.java create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/package-info.java create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateManager.java create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateSettings.java create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateSettingsV1.java create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/card/package-info.java create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/CardCertificateSignerSpi.java create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/package-info.java create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/package-info.java create mode 100644 src/main/javadoc/overview.html create mode 100644 src/test/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiPropertiesTest.java diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6225757 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,50 @@ +############################### +# Git Line Endings # +############################### + +* text=auto + +# CRLF for Windows files. +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf +*.cs text eol=crlf diff=csharp + +# LF for Linux files. +*.sh text eol=lf + +# Common files +*.java text eol=lf diff=java +*.html text eol=lf diff=html +*.css text eol=lf +*.js text eol=lf +*.sql text eol=lf + +############################### +# Git Large File System (LFS) # +############################### + +# Archives +*.7z filter=lfs diff=lfs merge=lfs -text +*.br filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.tar filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text + +# Documents +*.pdf filter=lfs diff=lfs merge=lfs -text +*.docx diff=astextplain merge=lfs -text + +# Images +*.gif filter=lfs diff=lfs merge=lfs -text +*.ico filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.pdf filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.psd filter=lfs diff=lfs merge=lfs -text +*.webp filter=lfs diff=lfs merge=lfs -text + +# Fonts +*.woff2 filter=lfs diff=lfs merge=lfs -text + +# Other +*.exe filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..01ddf38 --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +# Java class files +*.class + +# Generated files +gen/ +out/ +release/ + +# Gradle +.gradle/ +build/ +LICENSE_HEADER + +# Eclipse +.classpath +.project +.settings/ +bin/ + +# IntelliJ IDEA +.idea/ +*.iml +*.iws +*.ipr + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# MacOS +.DS_Store + +# Linux +*~ + +# Windows +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db +*.stackdump \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..913757e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +[unreleased]: https://github.com/eclipse-keypop/keypop-calypso-certificate-java-api/compare/0.1.0...HEAD \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..faa735b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,93 @@ +# Community Code of Conduct + +**Version 2.0 +January 1, 2023** + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as community members, contributors, Committers[^1], and Project Leads (collectively "Contributors") pledge to make participation in our projects and our community a harassment-free and inclusive experience for everyone. + +This Community Code of Conduct ("Code") outlines our behavior expectations as members of our community in all Eclipse Foundation activities, both offline and online. It is not intended to govern scenarios or behaviors outside of the scope of Eclipse Foundation activities. Nor is it intended to replace or supersede the protections offered to all our community members under the law. Please follow both the spirit and letter of this Code and encourage other Contributors to follow these principles into our work. Failure to read or acknowledge this Code does not excuse a Contributor from compliance with the Code. + +## Our Standards + +Examples of behavior that contribute to creating a positive and professional environment include: + +- Using welcoming and inclusive language; +- Actively encouraging all voices; +- Helping others bring their perspectives and listening actively. If you find yourself dominating a discussion, it is especially important to encourage other voices to join in; +- Being respectful of differing viewpoints and experiences; +- Gracefully accepting constructive criticism; +- Focusing on what is best for the community; +- Showing empathy towards other community members; +- Being direct but professional; and +- Leading by example by holding yourself and others accountable + +Examples of unacceptable behavior by Contributors include: + +- The use of sexualized language or imagery; +- Unwelcome sexual attention or advances; +- Trolling, insulting/derogatory comments, and personal or political attacks; +- Public or private harassment, repeated harassment; +- Publishing others' private information, such as a physical or electronic address, without explicit permission; +- Violent threats or language directed against another person; +- Sexist, racist, or otherwise discriminatory jokes and language; +- Posting sexually explicit or violent material; +- Sharing private content, such as emails sent privately or non-publicly, or unlogged forums such as IRC channel history; +- Personal insults, especially those using racist or sexist terms; +- Excessive or unnecessary profanity; +- Advocating for, or encouraging, any of the above behavior; and +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +With the support of the Eclipse Foundation employees, consultants, officers, and directors (collectively, the "Staff"), Committers, and Project Leads, the Eclipse Foundation Conduct Committee (the "Conduct Committee") is responsible for clarifying the standards of acceptable behavior. The Conduct Committee takes appropriate and fair corrective action in response to any instances of unacceptable behavior. + +## Scope + +This Code applies within all Project, Working Group, and Interest Group spaces and communication channels of the Eclipse Foundation (collectively, "Eclipse spaces"), within any Eclipse-organized event or meeting, and in public spaces when an individual is representing an Eclipse Foundation Project, Working Group, Interest Group, or their communities. Examples of representing a Project or community include posting via an official social media account, personal accounts, or acting as an appointed representative at an online or offline event. Representation of Projects, Working Groups, and Interest Groups may be further defined and clarified by Committers, Project Leads, or the Staff. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the Conduct Committee via conduct@eclipse-foundation.org. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Without the explicit consent of the reporter, the Conduct Committee is obligated to maintain confidentiality with regard to the reporter of an incident. The Conduct Committee is further obligated to ensure that the respondent is provided with sufficient information about the complaint to reply. If such details cannot be provided while maintaining confidentiality, the Conduct Committee will take the respondent‘s inability to provide a defense into account in its deliberations and decisions. Further details of enforcement guidelines may be posted separately. + +Staff, Committers and Project Leads have the right to report, remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code, or to block temporarily or permanently any Contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. Any such actions will be reported to the Conduct Committee for transparency and record keeping. + +Any Staff (including officers and directors of the Eclipse Foundation), Committers, Project Leads, or Conduct Committee members who are the subject of a complaint to the Conduct Committee will be recused from the process of resolving any such complaint. + +## Responsibility + +The responsibility for administering this Code rests with the Conduct Committee, with oversight by the Executive Director and the Board of Directors. For additional information on the Conduct Committee and its process, please write to . + +## Investigation of Potential Code Violations + +All conflict is not bad as a healthy debate may sometimes be necessary to push us to do our best. It is, however, unacceptable to be disrespectful or offensive, or violate this Code. If you see someone engaging in objectionable behavior violating this Code, we encourage you to address the behavior directly with those involved. If for some reason, you are unable to resolve the matter or feel uncomfortable doing so, or if the behavior is threatening or harassing, please report it following the procedure laid out below. + +Reports should be directed to . It is the Conduct Committee’s role to receive and address reported violations of this Code and to ensure a fair and speedy resolution. + +The Eclipse Foundation takes all reports of potential Code violations seriously and is committed to confidentiality and a full investigation of all allegations. The identity of the reporter will be omitted from the details of the report supplied to the accused. Contributors who are being investigated for a potential Code violation will have an opportunity to be heard prior to any final determination. Those found to have violated the Code can seek reconsideration of the violation and disciplinary action decisions. Every effort will be made to have all matters disposed of within 60 days of the receipt of the complaint. + +## Actions +Contributors who do not follow this Code in good faith may face temporary or permanent repercussions as determined by the Conduct Committee. + +This Code does not address all conduct. It works in conjunction with our [Communication Channel Guidelines](https://www.eclipse.org/org/documents/communication-channel-guidelines/), [Social Media Guidelines](https://www.eclipse.org/org/documents/social_media_guidelines.php), [Bylaws](https://www.eclipse.org/org/documents/eclipse-foundation-be-bylaws-en.pdf), and [Internal Rules](https://www.eclipse.org/org/documents/ef-be-internal-rules.pdf) which set out additional protections for, and obligations of, all contributors. The Foundation has additional policies that provide further guidance on other matters. + +It’s impossible to spell out every possible scenario that might be deemed a violation of this Code. Instead, we rely on one another’s good judgment to uphold a high standard of integrity within all Eclipse Spaces. Sometimes, identifying the right thing to do isn’t an easy call. In such a scenario, raise the issue as early as possible. + +## No Retaliation + +The Eclipse community relies upon and values the help of Contributors who identify potential problems that may need to be addressed within an Eclipse Space. Any retaliation against a Contributor who raises an issue honestly is a violation of this Code. That a Contributor has raised a concern honestly or participated in an investigation, cannot be the basis for any adverse action, including threats, harassment, or discrimination. If you work with someone who has raised a concern or provided information in an investigation, you should continue to treat the person with courtesy and respect. If you believe someone has retaliated against you, report the matter as described by this Code. Honest reporting does not mean that you have to be right when you raise a concern; you just have to believe that the information you are providing is accurate. + +False reporting, especially when intended to retaliate or exclude, is itself a violation of this Code and will not be accepted or tolerated. + +Everyone is encouraged to ask questions about this Code. Your feedback is welcome, and you will get a response within three business days. Write to . + +## Amendments + +The Eclipse Foundation Board of Directors may amend this Code from time to time and may vary the procedures it sets out where appropriate in a particular case. + +### Attribution + +This Code was inspired by the [Contributor Covenant](https://www.contributor-covenant.org/), version 1.4, available [here](https://www.contributor-covenant.org/version/1/4/code-of-conduct/). + +[^1]: Capitalized terms used herein without definition shall have the meanings assigned to them in the Bylaws. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4609d01 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,43 @@ +# Contributing to 'Eclipse Keypop' + +**Welcome to the Eclipse Keypop Community!** + +We are thrilled to have you onboard and look forward to your contributions. Whether you're a coder, designer, +documenter, or enthusiast, your involvement is invaluable to us. Here's how you can start contributing to Eclipse +Keypop: + +## Quick Start + +1. **Read the Contribution Guidelines**: Before starting, please visit our detailed contributing guide at the +[Eclipse Keypop Contribution Guide](https://eclipse-keypop.github.io/keypop-website/community/contributing/). +This guide covers all the necessary information about contributing to the project. + +2. **Join the mailing list**: Connect with other contributors on our +[mailing list](https://accounts.eclipse.org/mailing-list/keypop-dev/). +It's a great place to ask questions, share ideas, and collaborate. + +3. **Explore Open Issues**: Check out the issues tab in our GitHub repository. + +## How to Contribute + +- **Code**: Submit pull requests with bug fixes, new features, and improvements. Make sure to follow the code style and +testing guidelines. + +- **Documentation**: Help us improve our documentation by fixing errors, adding examples, or writing tutorials. + +- **Feedback**: Share your experience using Eclipse Keypop. Feedback on usability, features, and your overall experience +is incredibly valuable. + +- **Community Support**: Answer questions on the community forums, help with user support, and participate in +discussions. + +## Need Help? + +If you have any questions or need assistance, please reach out on the +[mailing list](https://accounts.eclipse.org/mailing-list/keypop-dev/). + +--- + +_Your contribution is the key to the success of Eclipse Keypop. Let's build something amazing together!_ + +--- \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..012a641 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,83 @@ +#!groovy +pipeline { + environment { + PROJECT_NAME = "keypop-calypso-certificate-java-api" + PROJECT_BOT_NAME = "Eclipse Keypop Bot" + } + agent { kubernetes { yaml javaBuilder('1.0') } } + stages { + stage('Import keyring') { + when { expression { env.GIT_URL.startsWith('https://github.com/eclipse-keypop/keypop-') && env.CHANGE_ID == null } } + steps { container('java-builder') { + withCredentials([file(credentialsId: 'secret-subkeys.asc', variable: 'KEYRING')]) { sh 'import_gpg "${KEYRING}"' } + } } + } + stage('Prepare settings') { steps { container('java-builder') { + script { + env.KEYPOP_VERSION = sh(script: 'grep version gradle.properties | cut -d= -f2 | tr -d "[:space:]"', returnStdout: true).trim() + env.GIT_COMMIT_MESSAGE = sh(script: 'git log --format=%B -1 | head -1 | tr -d "\n"', returnStdout: true) + echo "Building version ${env.KEYPOP_VERSION} in branch ${env.GIT_BRANCH}" + deployRelease = env.GIT_URL == "https://github.com/eclipse-keypop/${env.PROJECT_NAME}.git" && (env.GIT_BRANCH == "main" || env.GIT_BRANCH == "release-${env.KEYPOP_VERSION}") && env.CHANGE_ID == null && env.GIT_COMMIT_MESSAGE.startsWith("Release ${env.KEYPOP_VERSION}") + deploySnapshot = !deployRelease && env.GIT_URL == "https://github.com/eclipse-keypop/${env.PROJECT_NAME}.git" && (env.GIT_BRANCH == "main" || env.GIT_BRANCH == "release-${env.KEYPOP_VERSION}") && env.CHANGE_ID == null + } + sh "chmod +x ./gradlew ./scripts/*.sh" + } } } + stage('Check version') { + steps { container('java-builder') { + sh "./scripts/check_version.sh ${env.KEYPOP_VERSION}" + } } + } + stage('Build and Test') { + when { expression { !deploySnapshot && !deployRelease } } + steps { container('java-builder') { + sh './gradlew clean build test --no-build-cache --info --stacktrace' + junit testResults: 'build/test-results/test/*.xml', allowEmptyResults: true + } } + } + stage('Build and Publish Snapshot') { + when { expression { deploySnapshot } } + steps { container('java-builder') { + configFileProvider([configFile(fileId: 'gradle.properties', targetLocation: '/home/jenkins/agent/gradle.properties')]) { + sh './gradlew clean build test publish --info --stacktrace' + } + junit testResults: 'build/test-results/test/*.xml', allowEmptyResults: true + } } + } + stage('Build and Publish Release') { + when { expression { deployRelease } } + steps { container('java-builder') { + configFileProvider([configFile(fileId: 'gradle.properties', targetLocation: '/home/jenkins/agent/gradle.properties')]) { + sh './gradlew clean build test release --info --stacktrace' + } + junit testResults: 'build/test-results/test/*.xml', allowEmptyResults: true + } } + } + stage('Update GitHub Pages') { + when { expression { deploySnapshot || deployRelease } } + steps { container('java-builder') { + sh "./scripts/prepare_javadoc.sh ${env.PROJECT_NAME} ${env.KEYPOP_VERSION} ${deploySnapshot}" + dir("${env.PROJECT_NAME}") { + withCredentials([usernamePassword(credentialsId: 'github-bot', passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME')]) { + sh ''' + git add -A + git config user.email "${PROJECT_NAME}-bot@eclipse.org" + git config user.name "${PROJECT_BOT_NAME}" + git commit --allow-empty -m "docs: update documentation ${JOB_NAME}-${BUILD_NUMBER}" + git log --graph --abbrev-commit --date=relative -n 5 + git push "https://${GIT_USERNAME}:${GIT_PASSWORD}@github.com/eclipse-keypop/${PROJECT_NAME}.git" HEAD:gh-pages + ''' + } + } + } } + } + stage('Publish packaging to Eclipse') { + when { expression { deploySnapshot || deployRelease } } + steps { container('java-builder') { sshagent(['projects-storage.eclipse.org-bot-ssh']) { sh 'publish_packaging' } } } + } + } + post { always { container('java-builder') { + archiveArtifacts artifacts: 'build*/libs/**', allowEmptyArchive: true + archiveArtifacts artifacts: 'build*/reports/tests/**', allowEmptyArchive: true + archiveArtifacts artifacts: 'build*/reports/jacoco/test/html/**', allowEmptyArchive: true + } } } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4958beb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Calypso Networks Association + +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. diff --git a/NOTICE.md b/NOTICE.md new file mode 100644 index 0000000..2a51d9e --- /dev/null +++ b/NOTICE.md @@ -0,0 +1,34 @@ +# Notices for 'Eclipse Keypop' Java implementation + +This content is produced and maintained by the Eclipse Keypop project. + +* Project home: https://projects.eclipse.org/projects/iot.keypop + +## Supported platforms + +* Java SE 1.6 compact2 +* Android 4.4 KitKat API level 19 + +## Trademarks + +* Eclipse Keypop and the Eclipse Keypop project are Trademarks of the Eclipse Foundation, Inc. +* Eclipse® is a Trademark of the Eclipse Foundation, Inc. +* Eclipse Foundation is a Trademark of the Eclipse Foundation, Inc. + +## Copyright + +All content is the property of the respective authors or their employers. +For more information regarding authorship of content, please consult the +listed source code repository logs. + +## Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the MIT License which is available at +https://opensource.org/license/mit/ + +SPDX-License-Identifier: MIT + +## Third-party Content + +No third-party content. \ No newline at end of file diff --git a/PUBLISHERS.yml b/PUBLISHERS.yml new file mode 100644 index 0000000..43a920a --- /dev/null +++ b/PUBLISHERS.yml @@ -0,0 +1,18 @@ +url: https://github.com/eclipse-keypop/keypop-calypso-certificate-java-api +organization: + name: Eclipse Keypop + url: https://keypop.org/ +licenses: + - name: MIT license + url: https://opensource.org/license/mit/ + distribution: repo +developers: + - name: Keypop Contributors + email: keypop-dev@eclipse.org +scm: + connection: scm:git:git://github.com/eclipse-keypop/keypop-calypso-certificate-java-api.git + developerConnection: scm:git:https://github.com/eclipse-keypop/keypop-calypso-certificate-java-api.git + url: https://github.com/eclipse-keypop/keypop-calypso-certificate-java-api +ciManagement: + system: Jenkins + url: https://ci.eclipse.org/keypop/job/Keypop/job/keypop-calypso-certificate-java-api/ diff --git a/README.md b/README.md index ef2136b..de92af5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,23 @@ -# keypop-calypso-certificate-java-api -Eclipse Keypop project repository containing a Java implementation of the 'Calypso Certificate API' standardized by the Calypso Networks association for ticketing terminal processing smart card +# Keypop Calypso Certificate Java API + +## Overview + +This repository contains a Java implementation aligned with the **Terminal Calypso Certificate** specifications +proposed by the [Calypso Networks Association](https://www.calypsonet.org). It defines the generic interfaces required +to create Calypso certificates. + +## Documentation & Contribution Guide + +The full documentation, including the **user guide**, **download information** and **contribution guide**, is available +on the Keyple website [keypop.org](https://eclipse-keypop.github.io/keypop-website/). + +## API documentation + +API Javadoc is available [here](https://eclipse-keypop.github.io/keypop-calypso-certificate-java-api). + +API documentation and class diagram is available +[here](https://terminal-api.calypsonet.org/apis/calypsonet-terminal-calypso-certificate-api/). + +## About the source code + +The code is built with **Gradle** and is compliant with **Java 1.6** in order to address a wide range of applications. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..d0a3a3d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,9 @@ +# Security Policy + +This project implements the Eclipse Foundation Security Policy + +* https://www.eclipse.org/security + +## Reporting a Vulnerability + +Please report vulnerabilities to the Eclipse Foundation Security Team at security@eclipse.org \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..22a2101 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,59 @@ +/////////////////////////////////////////////////////////////////////////////// +// GRADLE CONFIGURATION +/////////////////////////////////////////////////////////////////////////////// +plugins { + java + id("com.diffplug.spotless") version "5.10.2" +} +buildscript { + repositories { + mavenLocal() + mavenCentral() + } + dependencies { + classpath("org.eclipse.keypop:keypop-gradle:0.1.+") { isChanging = true } + } +} +apply(plugin = "org.eclipse.keypop") + +/////////////////////////////////////////////////////////////////////////////// +// APP CONFIGURATION +/////////////////////////////////////////////////////////////////////////////// +repositories { + mavenLocal() + mavenCentral() +} +dependencies { + testImplementation("junit:junit:4.13.2") + testImplementation("org.assertj:assertj-core:3.15.0") +} + +val javaSourceLevel: String by project +val javaTargetLevel: String by project +java { + sourceCompatibility = JavaVersion.toVersion(javaSourceLevel) + targetCompatibility = JavaVersion.toVersion(javaTargetLevel) + println("Compiling Java $sourceCompatibility to Java $targetCompatibility.") + withJavadocJar() + withSourcesJar() +} + +/////////////////////////////////////////////////////////////////////////////// +// TASKS CONFIGURATION +/////////////////////////////////////////////////////////////////////////////// +tasks { + spotless { + java { + target("src/**/*.java") + licenseHeaderFile("${project.rootDir}/LICENSE_HEADER") + importOrder("java", "javax", "org", "com", "") + removeUnusedImports() + googleJavaFormat() + } + } + test { + testLogging { + events("passed", "skipped", "failed") + } + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..4f4aa52 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,15 @@ +group = org.eclipse.keypop +title = Keypop Calypso Certificate API +description = API dedicated to the creation of Calypso Certificates +version = 0.1.0 + +javaSourceLevel = 1.6 +javaTargetLevel = 1.6 + +# UTF-8 required by javadoc for special characters (ex. copyright) with Java 11+. +org.gradle.jvmargs="-Dfile.encoding=UTF-8" + +javadoc.logo = +javadoc.copyright = Copyright \u00a9 Calypso Networks Association https://calypsonet.org/ + +sonatype.url = https://oss.sonatype.org \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f GIT binary patch literal 59203 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

K7UpUK$>l@ zw`1S}(D?mUXu_C{wupRS-jiX~w=Uqqhf|Vb3Cm9L=T+w91Cu^ z*&Ty%sN?x*h~mJc4g~k{xD4ZmF%FXZNC;oVDwLZ_WvrnzY|{v8hc1nmx4^}Z;yriXsAf+Lp+OFLbR!&Ox?xABwl zu8w&|5pCxmu#$?Cv2_-Vghl2LZ6m7}VLEfR5o2Ou$x02uA-%QB2$c(c1rH3R9hesc zfpn#oqpbKuVsdfV#cv@5pV4^f_!WS+F>SV6N0JQ9E!T90EX((_{bSSFv9ld%I0&}9 zH&Jd4MEX1e0iqDtq~h?DBrxQX1iI0lIs<|kB$Yrh&cpeK0-^K%=FBsCBT46@h#yi!AyDq1V(#V}^;{{V*@T4WJ&U-NTq43w=|K>z8%pr_nC>%C(Wa_l78Ufib$r8Od)IIN=u>417 z`Hl{9A$mI5A(;+-Q&$F&h-@;NR>Z<2U;Y21>>Z;s@0V@SbkMQQj%_;~+qTuQ?c|AV zcWm3XZQHhP&R%QWarS%mJ!9R^&!_)*s(v+VR@I#QrAT}`17Y+l<`b-nvmDNW`De%y zrwTZ9EJrj1AFA>B`1jYDow}~*dfPs}IZMO3=a{Fy#IOILc8F0;JS4x(k-NSpbN@qM z`@aE_e}5{!$v3+qVs7u?sOV(y@1Os*Fgu`fCW9=G@F_#VQ%xf$hj0~wnnP0$hFI+@ zkQj~v#V>xn)u??YutKsX>pxKCl^p!C-o?+9;!Nug^ z{rP!|+KsP5%uF;ZCa5F;O^9TGac=M|=V z_H(PfkV1rz4jl?gJ(ArXMyWT4y(86d3`$iI4^l9`vLdZkzpznSd5Ikfrs8qcSy&>z zTIZgWZGXw0n9ibQxYWE@gI0(3#KA-dAdPcsL_|hg2@~C!VZDM}5;v_Nykfq!*@*Zf zE_wVgx82GMDryKO{U{D>vSzSc%B~|cjDQrt5BN=Ugpsf8H8f1lR4SGo#hCuXPL;QQ z#~b?C4MoepT3X`qdW2dNn& zo8)K}%Lpu>0tQei+{>*VGErz|qjbK#9 zvtd8rcHplw%YyQCKR{kyo6fgg!)6tHUYT(L>B7er5)41iG`j$qe*kSh$fY!PehLcD zWeKZHn<492B34*JUQh=CY1R~jT9Jt=k=jCU2=SL&&y5QI2uAG2?L8qd2U(^AW#{(x zThSy=C#>k+QMo^7caQcpU?Qn}j-`s?1vXuzG#j8(A+RUAY})F@=r&F(8nI&HspAy4 z4>(M>hI9c7?DCW8rw6|23?qQMSq?*Vx?v30U%luBo)B-k2mkL)Ljk5xUha3pK>EEj z@(;tH|M@xkuN?gsz;*bygizwYR!6=(Xgcg^>WlGtRYCozY<rFX2E>kaZo)O<^J7a`MX8Pf`gBd4vrtD|qKn&B)C&wp0O-x*@-|m*0egT=-t@%dD zgP2D+#WPptnc;_ugD6%zN}Z+X4=c61XNLb7L1gWd8;NHrBXwJ7s0ce#lWnnFUMTR& z1_R9Fin4!d17d4jpKcfh?MKRxxQk$@)*hradH2$3)nyXep5Z;B z?yX+-Bd=TqO2!11?MDtG0n(*T^!CIiF@ZQymqq1wPM_X$Iu9-P=^}v7npvvPBu!d$ z7K?@CsA8H38+zjA@{;{kG)#AHME>Ix<711_iQ@WWMObXyVO)a&^qE1GqpP47Q|_AG zP`(AD&r!V^MXQ^e+*n5~Lp9!B+#y3#f8J^5!iC@3Y@P`;FoUH{G*pj*q7MVV)29+j z>BC`a|1@U_v%%o9VH_HsSnM`jZ-&CDvbiqDg)tQEnV>b%Ptm)T|1?TrpIl)Y$LnG_ zzKi5j2Fx^K^PG1=*?GhK;$(UCF-tM~^=Z*+Wp{FSuy7iHt9#4n(sUuHK??@v+6*|10Csdnyg9hAsC5_OrSL;jVkLlf zHXIPukLqbhs~-*oa^gqgvtpgTk_7GypwH><53riYYL*M=Q@F-yEPLqQ&1Sc zZB%w}T~RO|#jFjMWcKMZccxm-SL)s_ig?OC?y_~gLFj{n8D$J_Kw%{r0oB8?@dWzn zB528d-wUBQzrrSSLq?fR!K%59Zv9J4yCQhhDGwhptpA5O5U?Hjqt>8nOD zi{)0CI|&Gu%zunGI*XFZh(ix)q${jT8wnnzbBMPYVJc4HX*9d^mz|21$=R$J$(y7V zo0dxdbX3N#=F$zjstTf*t8vL)2*{XH!+<2IJ1VVFa67|{?LP&P41h$2i2;?N~RA30LV`BsUcj zfO9#Pg1$t}7zpv#&)8`mis3~o+P(DxOMgz-V*(?wWaxi?R=NhtW}<#^Z?(BhSwyar zG|A#Q7wh4OfK<|DAcl9THc-W4*>J4nTevsD%dkj`U~wSUCh15?_N@uMdF^Kw+{agk zJ`im^wDqj`Ev)W3k3stasP`88-M0ZBs7;B6{-tSm3>I@_e-QfT?7|n0D~0RRqDb^G zyHb=is;IwuQ&ITzL4KsP@Z`b$d%B0Wuhioo1CWttW8yhsER1ZUZzA{F*K=wmi-sb#Ju+j z-l@In^IKnb{bQG}Ps>+Vu_W#grNKNGto+yjA)?>0?~X`4I3T@5G1)RqGUZuP^NJCq&^HykuYtMDD8qq+l8RcZNJsvN(10{ zQ1$XcGt}QH-U^WU!-wRR1d--{B$%vY{JLWIV%P4-KQuxxDeJaF#{eu&&r!3Qu{w}0f--8^H|KwE>)ORrcR+2Qf zb})DRcH>k0zWK8@{RX}NYvTF;E~phK{+F;MkIP$)T$93Ba2R2TvKc>`D??#mv9wg$ zd~|-`Qx5LwwsZ2hb*Rt4S9dsF%Cny5<1fscy~)d;0m2r$f=83<->c~!GNyb!U)PA; zq^!`@@)UaG)Ew(9V?5ZBq#c%dCWZrplmuM`o~TyHjAIMh0*#1{B>K4po-dx$Tk-Cq z=WZDkP5x2W&Os`N8KiYHRH#UY*n|nvd(U>yO=MFI-2BEp?x@=N<~CbLJBf6P)}vLS?xJXYJ2^<3KJUdrwKnJnTp{ zjIi|R=L7rn9b*D#Xxr4*R<3T5AuOS+#U8hNlfo&^9JO{VbH!v9^JbK=TCGR-5EWR@ zN8T-_I|&@A}(hKeL4_*eb!1G8p~&_Im8|wc>Cdir+gg90n1dw?QaXcx6Op_W1r=axRw>4;rM*UOpT#Eb9xU1IiWo@h?|5uP zka>-XW0Ikp@dIe;MN8B01a7+5V@h3WN{J=HJ*pe0uwQ3S&MyWFni47X32Q7SyCTNQ z+sR!_9IZa5!>f&V$`q!%H8ci!a|RMx5}5MA_kr+bhtQy{-^)(hCVa@I!^TV4RBi zAFa!Nsi3y37I5EK;0cqu|9MRj<^r&h1lF}u0KpKQD^5Y+LvFEwM zLU@@v4_Na#Axy6tn3P%sD^5P#<7F;sd$f4a7LBMk zGU^RZHBcxSA%kCx*eH&wgA?Qwazm8>9SCSz_!;MqY-QX<1@p$*T8lc?@`ikEqJ>#w zcG``^CoFMAhdEXT9qt47g0IZkaU)4R7wkGs^Ax}usqJ5HfDYAV$!=6?>J6+Ha1I<5 z|6=9soU4>E))tW$<#>F ziZ$6>KJf0bPfbx_)7-}tMINlc=}|H+$uX)mhC6-Hz+XZxsKd^b?RFB6et}O#+>Wmw9Ec9) z{q}XFWp{3@qmyK*Jvzpyqv57LIR;hPXKsrh{G?&dRjF%Zt5&m20Ll?OyfUYC3WRn{cgQ?^V~UAv+5 z&_m#&nIwffgX1*Z2#5^Kl4DbE#NrD&Hi4|7SPqZ}(>_+JMz=s|k77aEL}<=0Zfb)a z%F(*L3zCA<=xO)2U3B|pcTqDbBoFp>QyAEU(jMu8(jLA61-H!ucI804+B!$E^cQQa z)_ERrW3g!B9iLb3nn3dlkvD7KsY?sRvls3QC0qPi>o<)GHx%4Xb$5a3GBTJ(k@`e@ z$RUa^%S15^1oLEmA=sayrP5;9qtf!Z1*?e$ORVPsXpL{jL<6E)0sj&swP3}NPmR%FM?O>SQgN5XfHE< zo(4#Cv11(%Nnw_{_Ro}r6=gKd{k?NebJ~<~Kv0r(r0qe4n3LFx$5%x(BKvrz$m?LG zjLIc;hbj0FMdb9aH9Lpsof#yG$(0sG2%RL;d(n>;#jb!R_+dad+K;Ccw!|RY?uS(a zj~?=&M!4C(5LnlH6k%aYvz@7?xRa^2gml%vn&eKl$R_lJ+e|xsNfXzr#xuh(>`}9g zLHSyiFwK^-p!;p$yt7$F|3*IfO3Mlu9e>Dpx8O`37?fA`cj`C0B-m9uRhJjs^mRp# zWB;Aj6|G^1V6`jg7#7V9UFvnB4((nIwG?k%c7h`?0tS8J3Bn0t#pb#SA}N-|45$-j z$R>%7cc2ebAClXc(&0UtHX<>pd)akR3Kx_cK+n<}FhzmTx!8e9^u2e4%x{>T6pQ`6 zO182bh$-W5A3^wos0SV_TgPmF4WUP-+D25KjbC{y_6W_9I2_vNKwU(^qSdn&>^=*t z&uvp*@c8#2*paD!ZMCi3;K{Na;I4Q35zw$YrW5U@Kk~)&rw;G?d7Q&c9|x<Hg|CNMsxovmfth*|E*GHezPTWa^Hd^F4!B3sF;)? z(NaPyAhocu1jUe(!5Cy|dh|W2=!@fNmuNOzxi^tE_jAtzNJ0JR-avc_H|ve#KO}#S z#a(8secu|^Tx553d4r@3#6^MHbH)vmiBpn0X^29xEv!Vuh1n(Sr5I0V&`jA2;WS|Y zbf0e}X|)wA-Pf5gBZ>r4YX3Mav1kKY(ulAJ0Q*jB)YhviHK)w!TJsi3^dMa$L@^{` z_De`fF4;M87vM3Ph9SzCoCi$#Fsd38u!^0#*sPful^p5oI(xGU?yeYjn;Hq1!wzFk zG&2w}W3`AX4bxoVm03y>ts{KaDf!}b&7$(P4KAMP=vK5?1In^-YYNtx1f#}+2QK@h zeSeAI@E6Z8a?)>sZ`fbq9_snl6LCu6g>o)rO;ijp3|$vig+4t} zylEo7$SEW<_U+qgVcaVhk+4k+C9THI5V10qV*dOV6pPtAI$)QN{!JRBKh-D zk2^{j@bZ}yqW?<#VVuI_27*cI-V~sJiqQv&m07+10XF+#ZnIJdr8t`9s_EE;T2V;B z4UnQUH9EdX%zwh-5&wflY#ve!IWt0UE-My3?L#^Bh%kcgP1q{&26eXLn zTkjJ*w+(|_>Pq0v8{%nX$QZbf)tbJaLY$03;MO=Ic-uqYUmUCuXD>J>o6BCRF=xa% z3R4SK9#t1!K4I_d>tZgE>&+kZ?Q}1qo4&h%U$GfY058s%*=!kac{0Z+4Hwm!)pFLR zJ+5*OpgWUrm0FPI2ib4NPJ+Sk07j(`diti^i#kh&f}i>P4~|d?RFb#!JN)~D@)beox}bw?4VCf^y*`2{4`-@%SFTry2h z>9VBc9#JxEs1+0i2^LR@B1J`B9Ac=#FW=(?2;5;#U$0E0UNag_!jY$&2diQk_n)bT zl5Me_SUvqUjwCqmVcyb`igygB_4YUB*m$h5oeKv3uIF0sk}~es!{D>4r%PC*F~FN3owq5e0|YeUTSG#Vq%&Gk7uwW z0lDo#_wvflqHeRm*}l?}o;EILszBt|EW*zNPmq#?4A+&i0xx^?9obLyY4xx=Y9&^G;xYXYPxG)DOpPg!i_Ccl#3L}6xAAZzNhPK1XaC_~ z!A|mlo?Be*8Nn=a+FhgpOj@G7yYs(Qk(8&|h@_>w8Y^r&5nCqe0V60rRz?b5%J;GYeBqSAjo|K692GxD4` zRZyM2FdI+-jK2}WAZTZ()w_)V{n5tEb@>+JYluDozCb$fA4H)$bzg(Ux{*hXurjO^ zwAxc+UXu=&JV*E59}h3kzQPG4M)X8E*}#_&}w*KEgtX)cU{vm9b$atHa;s>| z+L6&cn8xUL*OSjx4YGjf6{Eq+Q3{!ZyhrL&^6Vz@jGbI%cAM9GkmFlamTbcQGvOlL zmJ?(FI)c86=JEs|*;?h~o)88>12nXlpMR4@yh%qdwFNpct;vMlc=;{FSo*apJ;p}! zAX~t;3tb~VuP|ZW;z$=IHf->F@Ml)&-&Bnb{iQyE#;GZ@C$PzEf6~q}4D>9jic@mTO5x76ulDz@+XAcm35!VSu zT*Gs>;f0b2TNpjU_BjHZ&S6Sqk6V1370+!eppV2H+FY!q*n=GHQ!9Rn6MjY!Jc77A zG7Y!lFp8?TIHN!LXO?gCnsYM-gQxsm=Ek**VmZu7vnuufD7K~GIxfxbsQ@qv2T zPa`tvHB$fFCyZl>3oYg?_wW)C>^_iDOc^B7klnTOoytQH18WkOk)L2BSD0r%xgRSW zQS9elF^?O=_@|58zKLK;(f77l-Zzu}4{fXed2saq!5k#UZAoDBqYQS{sn@j@Vtp|$ zG%gnZ$U|9@u#w1@11Sjl8ze^Co=)7yS(}=;68a3~g;NDe_X^}yJj;~s8xq9ahQ5_r zxAlTMnep*)w1e(TG%tWsjo3RR;yVGPEO4V{Zp?=a_0R#=V^ioQu4YL=BO4r0$$XTX zZfnw#_$V}sDAIDrezGQ+h?q24St0QNug_?{s-pI(^jg`#JRxM1YBV;a@@JQvH8*>> zIJvku74E0NlXkYe_624>znU0J@L<-c=G#F3k4A_)*;ky!C(^uZfj%WB3-*{*B$?9+ zDm$WFp=0(xnt6`vDQV3Jl5f&R(Mp};;q8d3I%Kn>Kx=^;uSVCw0L=gw53%Bp==8Sw zxtx=cs!^-_+i{2OK`Q;913+AXc_&Z5$@z3<)So0CU3;JAv=H?@Zpi~riQ{z-zLtVL z!oF<}@IgJp)Iyz1zVJ42!SPHSkjYNS4%ulVVIXdRuiZ@5Mx8LJS}J#qD^Zi_xQ@>DKDr-_e#>5h3dtje*NcwH_h;i{Sx7}dkdpuW z(yUCjckQsagv*QGMSi9u1`Z|V^}Wjf7B@q%j2DQXyd0nOyqg%m{CK_lAoKlJ7#8M} z%IvR?Vh$6aDWK2W!=i?*<77q&B8O&3?zP(Cs@kapc)&p7En?J;t-TX9abGT#H?TW? ztO5(lPKRuC7fs}zwcUKbRh=7E8wzTsa#Z{a`WR}?UZ%!HohN}d&xJ=JQhpO1PI#>X zHkb>pW04pU%Bj_mf~U}1F1=wxdBZu1790>3Dm44bQ#F=T4V3&HlOLsGH)+AK$cHk6 zia$=$kog?)07HCL*PI6}DRhpM^*%I*kHM<#1Se+AQ!!xyhcy6j7`iDX7Z-2i73_n# zas*?7LkxS-XSqv;YBa zW_n*32D(HTYQ0$feV_Fru1ZxW0g&iwqixPX3=9t4o)o|kOo79V$?$uh?#8Q8e>4e)V6;_(x&ViUVxma+i25qea;d-oK7ouuDsB^ab{ zu1qjQ%`n56VtxBE#0qAzb7lph`Eb-}TYpXB!H-}3Ykqyp`otprp7{VEuW*^IR2n$Fb99*nAtqT&oOFIf z@w*6>YvOGw@Ja?Pp1=whZqydzx@9X4n^2!n83C5{C?G@|E?&$?p*g68)kNvUTJ)I6 z1Q|(#UuP6pj78GUxq11m-GSszc+)X{C2eo-?8ud9sB=3(D47v?`JAa{V(IF zPZQ_0AY*9M97>Jf<o%#O_%Wq}8>YM=q0|tGY+hlXcpE=Z4Od z`NT7Hu2hnvRoqOw@g1f=bv`+nba{GwA$Ak0INlqI1k<9!x_!sL()h?hEWoWrdU3w` zZ%%)VR+Bc@_v!C#koM1p-3v_^L6)_Ktj4HE>aUh%2XZE@JFMOn)J~c`_7VWNb9c-N z2b|SZMR4Z@E7j&q&9(6H3yjEu6HV7{2!1t0lgizD;mZ9$r(r7W5G$ky@w(T_dFnOD z*p#+z$@pKE+>o@%eT(2-p_C}wbQ5s(%Sn_{$HDN@MB+Ev?t@3dPy`%TZ!z}AThZSu zN<1i$siJhXFdjV zP*y|V<`V8t=h#XTRUR~5`c`Z9^-`*BZf?WAehGdg)E2Je)hqFa!k{V(u+(hTf^Yq& zoruUh2(^3pe)2{bvt4&4Y9CY3js)PUHtd4rVG57}uFJL)D(JfSIo^{P=7liFXG zq5yqgof0V8paQcP!gy+;^pp-DA5pj=gbMN0eW=-eY+N8~y+G>t+x}oa!5r>tW$xhI zPQSv=pi;~653Gvf6~*JcQ%t1xOrH2l3Zy@8AoJ+wz@daW@m7?%LXkr!bw9GY@ns3e zSfuWF_gkWnesv?s3I`@}NgE2xwgs&rj?kH-FEy82=O8`+szN ziHch`vvS`zNfap14!&#i9H@wF7}yIPm=UB%(o(}F{wsZ(wA0nJ2aD^@B41>>o-_U6 zUqD~vdo48S8~FTb^+%#zcbQiiYoDKYcj&$#^;Smmb+Ljp(L=1Kt_J!;0s%1|JK}Wi z;={~oL!foo5n8=}rs6MmUW~R&;SIJO3TL4Ky?kh+b2rT9B1Jl4>#Uh-Bec z`Hsp<==#UEW6pGPhNk8H!!DUQR~#F9jEMI6T*OWfN^Ze&X(4nV$wa8QUJ>oTkruH# zm~O<`J7Wxseo@FqaZMl#Y(mrFW9AHM9Kb|XBMqaZ2a)DvJgYipkDD_VUF_PKd~dT7 z#02}bBfPn9a!X!O#83=lbJSK#E}K&yx-HI#T6ua)6o0{|={*HFusCkHzs|Fn&|C3H zBck1cmfcWVUN&i>X$YU^Sn6k2H;r3zuXbJFz)r5~3$d$tUj(l1?o={MM){kjgqXRO zc5R*#{;V7AQh|G|)jLM@wGAK&rm2~@{Pewv#06pHbKn#wL0P6F1!^qw9g&cW3Z=9} zj)POhOlwsh@eF=>z?#sIs*C-Nl(yU!#DaiaxhEs#iJqQ8w%(?+6lU02MYSeDkr!B- zPjMv+on6OLXgGnAtl(ao>|X2Y8*Hb}GRW5}-IzXnoo-d0!m4Vy$GS!XOLy>3_+UGs z2D|YcQx@M#M|}TDOetGi{9lGo9m-=0-^+nKE^*?$^uHkxZh}I{#UTQd;X!L+W@jm( zDg@N4+lUqI92o_rNk{3P>1gxAL=&O;x)ZT=q1mk0kLlE$WeWuY_$0`0jY-Kkt zP*|m3AF}Ubd=`<>(Xg0har*_@x2YH}bn0Wk*OZz3*e5;Zc;2uBdnl8?&XjupbkOeNZsNh6pvsq_ydmJI+*z**{I{0K)-;p1~k8cpJXL$^t!-`E}=*4G^-E8>H!LjTPxSx zcF+cS`ommfKMhNSbas^@YbTpH1*RFrBuATUR zt{oFWSk^$xU&kbFQ;MCX22RAN5F6eq9UfR$ut`Jw--p2YX)A*J69m^!oYfj2y7NYcH6&r+0~_sH^c^nzeN1AU4Ga7=FlR{S|Mm~MpzY0$Z+p2W(a={b-pR9EO1Rs zB%KY|@wLcAA@)KXi!d2_BxrkhDn`DT1=Dec}V!okd{$+wK z4E{n8R*xKyci1(CnNdhf$Dp2(Jpof0-0%-38X=Dd9PQgT+w%Lshx9+loPS~MOm%ZT zt%2B2iL_KU_ita%N>xjB!#71_3=3c}o zgeW~^U_ZTJQ2!PqXulQd=3b=XOQhwATK$y(9$#1jOQ4}4?~l#&nek)H(04f(Sr=s| zWv7Lu1=%WGk4FSw^;;!8&YPM)pQDCY9DhU`hMty1@sq1=Tj7bFsOOBZOFlpR`W>-J$-(kezWJj;`?x-v>ev{*8V z8p|KXJPV$HyQr1A(9LVrM47u-XpcrIyO`yWvx1pVYc&?154aneRpLqgx)EMvRaa#|9?Wwqs2+W8n5~79G z(}iCiLk;?enn}ew`HzhG+tu+Ru@T+K5juvZN)wY;x6HjvqD!&!)$$;1VAh~7fg0K| zEha#aN=Yv|3^~YFH}cc38ovVb%L|g@9W6fo(JtT6$fa?zf@Ct88e}m?i)b*Jgc{fl zExfdvw-BYDmH6>(4QMt#p0;FUIQqkhD}aH?a7)_%JtA~soqj{ppP_82yi9kaxuK>~ ze_)Zt>1?q=ZH*kF{1iq9sr*tVuy=u>Zev}!gEZx@O6-fjyu9X00gpIl-fS_pzjpqJ z1yqBmf9NF!jaF<+YxgH6oXBdK)sH(>VZ)1siyA$P<#KDt;8NT*l_0{xit~5j1P)FN zI8hhYKhQ)i z37^aP13B~u65?sg+_@2Kr^iWHN=U;EDSZ@2W2!5ALhGNWXnFBY%7W?1 z=HI9JzQ-pLKZDYTv<0-lt|6c-RwhxZ)mU2Os{bsX_i^@*fKUj8*aDO5pks=qn3Dv6 zwggpKLuyRCTVPwmw1r}B#AS}?X7b837UlXwp~E2|PJw2SGVueL7){Y&z!jL!XN=0i zU^Eig`S2`{+gU$68aRdWx?BZ{sU_f=8sn~>s~M?GU~`fH5kCc; z8ICp+INM3(3{#k32RZdv6b9MQYdZXNuk7ed8;G?S2nT+NZBG=Tar^KFl2SvhW$bGW#kdWL-I)s_IqVnCDDM9fm8g;P;8 z7t4yZn3^*NQfx7SwmkzP$=fwdC}bafQSEF@pd&P8@H#`swGy_rz;Z?Ty5mkS%>m#% zp_!m9e<()sfKiY(nF<1zBz&&`ZlJf6QLvLhl`_``%RW&{+O>Xhp;lwSsyRqGf=RWd zpftiR`={2(siiPAS|p}@q=NhVc0ELprt%=fMXO3B)4ryC2LT(o=sLM7hJC!}T1@)E zA3^J$3&1*M6Xq>03FX`R&w*NkrZE?FwU+Muut;>qNhj@bX17ZJxnOlPSZ=Zeiz~T_ zOu#yc3t6ONHB;?|r4w+pI)~KGN;HOGC)txxiUN8#mexj+W(cz%9a4sx|IRG=}ia zuEBuba3AHsV2feqw-3MvuL`I+2|`Ud4~7ZkN=JZ;L20|Oxna5vx1qbIh#k2O4$RQF zo`tL()zxaqibg^GbB+BS5#U{@K;WWQj~GcB1zb}zJkPwH|5hZ9iH2308!>_;%msji zJHSL~s)YHBR=Koa1mLEOHos*`gp=s8KA-C zu0aE+W!#iJ*0xqKm3A`fUGy#O+X+5W36myS>Uh2!R*s$aCU^`K&KKLCCDkejX2p=5 z%o7-fl03x`gaSNyr?3_JLv?2RLS3F*8ub>Jd@^Cc17)v8vYEK4aqo?OS@W9mt%ITJ z9=S2%R8M){CugT@k~~0x`}Vl!svYqX=E)c_oU6o}#Hb^%G1l3BudxA{F*tbjG;W_>=xV73pKY53v%>I)@D36I_@&p$h|Aw zonQS`07z_F#@T-%@-Tb|)7;;anoD_WH>9ewFy(ZcEOM$#Y)8>qi7rCnsH9GO-_7zF zu*C87{Df1P4TEOsnzZ@H%&lvV(3V@;Q!%+OYRp`g05PjY^gL$^$-t0Y>H*CDDs?FZly*oZ&dxvsxaUWF!{em4{A>n@vpXg$dwvt@_rgmHF z-MER`ABa8R-t_H*kv>}CzOpz;!>p^^9ztHMsHL|SRnS<-y5Z*r(_}c4=fXF`l^-i}>e7v!qs_jv zqvWhX^F=2sDNWA9c@P0?lUlr6ecrTKM%pNQ^?*Lq?p-0~?_j50xV%^(+H>sMul#Tw zeciF*1=?a7cI(}352%>LO96pD+?9!fNyl^9v3^v&Y4L)mNGK0FN43&Xf8jUlxW1Bw zyiu2;qW-aGNhs=zbuoxnxiwZ3{PFZM#Kw)9H@(hgX23h(`Wm~m4&TvoZoYp{plb^> z_#?vXcxd>r7K+1HKJvhed>gtK`TAbJUazUWQY6T~t2af%#<+Veyr%7-#*A#@&*;@g58{i|E%6yC_InGXCOd{L0;$)z#?n7M`re zh!kO{6=>7I?*}czyF7_frt#)s1CFJ_XE&VrDA?Dp3XbvF{qsEJgb&OLSNz_5g?HpK z9)8rsr4JN!Af3G9!#Qn(6zaUDqLN(g2g8*M)Djap?WMK9NKlkC)E2|-g|#-rp%!Gz zAHd%`iq|81efi93m3yTBw3g0j#;Yb2X{mhRAI?&KDmbGqou(2xiRNb^sV}%%Wu0?< z?($L>(#BO*)^)rSgyNRni$i`R4v;GhlCZ8$@e^ROX(p=2_v6Y!%^As zu022)fHdv_-~Yu_H6WVPLpHQx!W%^6j)cBhS`O3QBW#x(eX54d&I22op(N59b*&$v zFiSRY6rOc^(dgSV1>a7-5C;(5S5MvKcM2Jm-LD9TGqDpP097%52V+0>Xqq!! zq4e3vj53SE6i8J`XcQB|MZPP8j;PAOnpGnllH6#Ku~vS42xP*Nz@~y%db7Xi8s09P z1)e%8ys6&M8D=Dt6&t`iKG_4X=!kgRQoh%Z`dc&mlOUqXk-k`jKv9@(a^2-Upw>?< zt5*^DV~6Zedbec4NVl($2T{&b)zA@b#dUyd>`2JC0=xa_fIm8{5um zr-!ApXZhC8@=vC2WyxO|!@0Km)h8ep*`^he92$@YwP>VcdoS5OC^s38e#7RPsg4j+ zbVGG}WRSET&ZfrcR(x~k8n1rTP%CnfUNKUonD$P?FtNFF#cn!wEIab-;jU=B1dHK@ z(;(yAQJ`O$sMn>h;pf^8{JISW%d+@v6@CnXh9n5TXGC}?FI9i-D0OMaIg&mAg=0Kn zNJ7oz5*ReJukD55fUsMuaP+H4tDN&V9zfqF@ zr=#ecUk9wu{0;!+gl;3Bw=Vn^)z$ahVhhw)io!na&9}LmWurLb0zubxK=UEnU*{5P z+SP}&*(iBKSO4{alBHaY^)5Q=mZ+2OwIooJ7*Q5XJ+2|q`9#f?6myq!&oz?klihLq z4C)$XP!BNS0G_Z1&TM>?Jk{S~{F3n83ioli=IO6f%wkvCl(RFFw~j0tb{GvXTx>*sB0McY0s&SNvj4+^h`9nJ_wM>F!Uc>X}9PifQekn0sKI2SAJP!a4h z5cyGTuCj3ZBM^&{dRelIlT^9zcfaAuL5Y~bl!ppSf`wZbK$z#6U~rdclk``e+!qhe z6Qspo*%<)eu6?C;Bp<^VuW6JI|Ncvyn+LlSl;Mp22Bl7ARQ0Xc24%29(ZrdsIPw&-=yHQ7_Vle|5h>AST0 zUGX2Zk34vp?U~IHT|;$U86T+UUHl_NE4m|}>E~6q``7hccCaT^#y+?wD##Q%HwPd8 zV3x4L4|qqu`B$4(LXqDJngNy-{&@aFBvVsywt@X^}iH7P%>bR?ciC$I^U-4Foa`YKI^qDyGK7k%E%c_P=yzAi`YnxGA%DeNd++j3*h^ z=rn>oBd0|~lZ<6YvmkKY*ZJlJ;Im0tqgWu&E92eqt;+NYdxx`eS(4Hw_Jb5|yVvBg z*tbdY^!AN;luEyN4VRhS@-_DC{({ziH{&Z}iGElSV~qvT>L-8G%+yEL zX#MFOhj{InyKG=mvW-<1B@c-}x$vA(nU?>S>0*eN#!SLzQ)Ex7fvQ)S4D<8|I#N$3 zT5Ei`Z?cxBODHX8(Xp73v`IsAYC@9b;t}z0wxVuQSY1J^GRwDPN@qbM-ZF48T$GZ< z8WU+;Pqo?{ghI-KZ-i*ydXu`Ep0Xw^McH_KE9J0S7G;x8Fe`DVG?j3Pv=0YzJ}yZR z%2=oqHiUjvuk0~Ca>Kol4CFi0_xQT~;_F?=u+!kIDl-9g`#ZNZ9HCy17Ga1v^Jv9# z{T4Kb1-AzUxq*MutfOWWZgD*HnFfyYg0&e9f(5tZ>krPF6{VikNeHoc{linPPt#Si z&*g>(c54V8rT_AX!J&bNm-!umPvOR}vDai#`CX___J#=zeB*{4<&2WpaDncZsOkp* zsg<%@@rbrMkR_ux9?LsQxzoBa1s%$BBn6vk#{&&zUwcfzeCBJUwFYSF$08qDsB;gWQN*g!p8pxjofWbqNSZOEKOaTx@+* zwdt5*Q47@EOZ~EZL9s?1o?A%9TJT=Ob_13yyugvPg*e&ZU(r6^k4=2+D-@n=Hv5vu zSXG|hM(>h9^zn=eQ=$6`JO&70&2|%V5Lsx>)(%#;pcOfu>*nk_3HB_BNaH$`jM<^S zcSftDU1?nL;jy)+sfonQN}(}gUW?d_ikr*3=^{G)=tjBtEPe>TO|0ddVB zTklrSHiW+!#26frPXQQ(YN8DG$PZo?(po(QUCCf_OJC`pw*uey00%gmH!`WJkrKXj2!#6?`T25mTu9OJp2L8z3! z=arrL$ZqxuE{%yV)14Kd>k}j7pxZ6#$Dz8$@WV5p8kTqN<-7W)Q7Gt2{KoOPK_tZ| zf2WG~O5@{qPI+W<4f_;reuFVdO^5`ADC1!JQE|N`s3cq@(0WB!n0uh@*c{=LAd;~} zyGK@hbF-Oo+!nN)@i*O(`@FA#u?o=~e{`4O#5}z&=UkU*50fOrzi11D^&FOqe>wii z?*k+2|EcUs;Gx{!@KBT~>PAwLrIDT7Th=Utu?~?np@t^gFs?zgX=D${RwOY^WGh-+ z+#4$066ISh8eYW#FXWp~S`<*%O^ZuItL1Tyqt8#tZ zY120E;^VG`!lZn&3sPd$RkdHpU#|w+bYV)pJC|SH9g%|5IkxVTQcBA4CL0}$&}ef@ zW^Vtj%M;;_1xxP9x#ex17&4N*{ksO*_4O}xYu(p*JkL#yr}@7b)t5X?%CY<+s5_MJ zuiqt+N_;A(_)%lumoyRFixWa-M7qK_9s6<1X?JDa9fP!+_6u~~M$5L=ipB=7(j#f< zZ34J%=bs549%~_mA(|={uZNs_0?o7;-LBP(ZRnkd{-^|2|=4vUTmtByHL8 zEph`(LSEzQj68a+`d$V<45J7cyv^#|^|%fD#si1Nx!4NW*`l*{->HEWNh6-|g>-=r zXmQ|-i}Ku$ndUeHQ^&ieT!Lf}vf6GaqW9$DJ2NWrqwPY%%4nip$@vK$nRp*_C-v<| zuKz~ZyN&<%!NS26&x?jhy+@awJipMQ-8(X4#Ae5??U<1QMt1l9R=w9fAnEF}NYu$2 z>6}Vkc zIb*A?G*z8^IvibmBKn_u^5&T_1oey0gZS2~obf(#xk=erZGTEdQnt3DMGM+0oPwss zj5zXD;(oWhB_T@~Ig#9@v)AKtXu3>Inmgf@A|-lD-1U>cNyl3h?ADD9)GG4}zUGPk zZzaXe!~Kf?<~@$G?Uql3t8jy9{2!doq4=J}j9ktTxss{p6!9UdjyDERlA*xZ!=Q)KDs5O)phz>Vq3BNGoM(H|=1*Q4$^2fTZw z(%nq1P|5Rt81}SYJpEEzMPl5VJsV5&4e)ZWKDyoZ>1EwpkHx-AQVQc8%JMz;{H~p{=FXV>jIxvm4X*qv52e?Y-f%DJ zxEA165GikEASQ^fH6K#d!Tpu2HP{sFs%E=e$gYd$aj$+xue6N+Wc(rAz~wUsk2`(b z8Kvmyz%bKQxpP}~baG-rwYcYCvkHOi zlkR<=>ZBTU*8RF_d#Bl@zZsRIhx<%~Z@Z=ik z>adw3!DK(8R|q$vy{FTxw%#xliD~6qXmY^7_9kthVPTF~Xy1CfBqbU~?1QmxmU=+k z(ggxvEuA;0e&+ci-zQR{-f7aO{O(Pz_OsEjLh_K>MbvoZ4nxtk5u{g@nPv)cgW_R} z9}EA4K4@z0?7ue}Z(o~R(X&FjejUI2g~08PH1E4w>9o{)S(?1>Z0XMvTb|;&EuyOE zGvWNpYX)Nv<8|a^;1>bh#&znEcl-r!T#pn= z4$?Yudha6F%4b>*8@=BdtXXY4N+`U4Dmx$}>HeVJk-QdTG@t!tVT#0(LeV0gvqyyw z2sEp^9eY0N`u10Tm4n8No&A=)IeEC|gnmEXoNSzu!1<4R<%-9kY_8~5Ej?zRegMn78wuMs#;i&eUA0Zk_RXQ3b&TT} z;SCI=7-FUB@*&;8|n>(_g^HGf3@QODE3LpmX~ELnymQm{Sx9xrKS zK29p~?v@R$0=v6Dr5aW>-!{+h@?Q58|Kz8{{W`%J+lDAdb&M5VHrX_mDY;1-JLnf)ezmPau$)1;=`-FU=-r-83tX=C`S#}GZufju zQ>sXNT0Ny=k@nc%cFnvA_i4SC)?_ORXHq8B4D%el1uPX`c~uG#S1M7C+*MMqLw78E zhY2dI8@+N^qrMI1+;TUda(vGqGSRyU{Fnm`aqrr7bz42c5xsOO-~oZpkzorD1g}Y<6rk&3>PsSGy}W?MtqFky@A(X# zIuNZK0cK?^=;PUAu>j0#HtjbHCV*6?jzA&OoE$*Jlga*}LF`SF?WLhv1O|zqC<>*> zYB;#lsYKx0&kH@BFpW8n*yDcc6?;_zaJs<-jPSkCsSX-!aV=P5kUgF@Nu<{a%#K*F z134Q{9|YX7X(v$62_cY3^G%t~rD>Q0z@)1|zs)vjJ6Jq9;7#Ki`w+eS**En?7;n&7 zu==V3T&eFboN3ZiMx3D8qYc;VjFUk_H-WWCau(VFXSQf~viH0L$gwD$UfFHqNcgN`x}M+YQ6RnN<+@t>JUp#)9YOkqst-Ga?{FsDpEeX0(5v{0J~SEbWiL zXC2}M4?UH@u&|;%0y`eb33ldo4~z-x8zY!oVmV=c+f$m?RfDC35mdQ2E>Pze7KWP- z>!Bh<&57I+O_^s}9Tg^k)h7{xx@0a0IA~GAOt2yy!X%Q$1rt~LbTB6@Du!_0%HV>N zlf)QI1&gvERKwso23mJ!Ou6ZS#zCS5W`gxE5T>C#E|{i<1D35C222I33?Njaz`On7 zi<+VWFP6D{e-{yiN#M|Jgk<44u1TiMI78S5W`Sdb5f+{zu34s{CfWN7a3Cf^@L%!& zN$?|!!9j2c)j$~+R6n#891w-z8(!oBpL2K=+%a$r2|~8-(vQj5_XT`<0Ksf;oP+tz z9CObS!0m)Tgg`K#xBM8B(|Z)Wb&DYL{WTYv`;A=q6~Nnx2+!lTIXtj8J7dZE!P_{z z#f8w6F}^!?^KE#+ZDv+xd5O&3EmomZzsv?>E-~ygGum45fk!SBN&|eo1rKw^?aZJ4 E2O(~oYXATM literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..442d913 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# 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 +# +# https://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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +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 + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/scripts/check_version.sh b/scripts/check_version.sh new file mode 100644 index 0000000..4417fc0 --- /dev/null +++ b/scripts/check_version.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +version=$1 +echo "Input version : '$version'" + +echo "Fetch tags..." +git fetch --tags + +if [ $(git tag -l "$version") ]; then + echo "ERROR: version '$version' has already been released" + exit 1 +fi diff --git a/scripts/prepare_javadoc.sh b/scripts/prepare_javadoc.sh new file mode 100644 index 0000000..12f479b --- /dev/null +++ b/scripts/prepare_javadoc.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +echo "Compute the current API version..." + +repository_name=$1 +version=$2 +is_snapshot=$3 + +if [ "$is_snapshot" = true ] +then + version="$version-SNAPSHOT" +fi + +echo "Computed current API version: $version" + +echo "Clone $repository_name..." +git clone https://github.com/eclipse-keypop/$repository_name.git + +cd $repository_name + +echo "Checkout gh-pages branch..." +git checkout -f gh-pages + +echo "Delete existing SNAPSHOT directory..." +rm -rf *-SNAPSHOT + +echo "Delete existing RC directories in case of final release..." +rm -rf $version-rc* + +echo "Create target directory $version..." +mkdir $version + +echo "Copy javadoc files..." +cp -rf ../build/docs/javadoc/* $version/ + +echo "Update versions list..." +echo "| Version | Documents |" > list_versions.md +echo "|:---:|---|" >> list_versions.md +for directory in `ls -rd [0-9]*/ | cut -f1 -d'/'` +do + echo "| $directory | [API documentation]($directory) |" >> list_versions.md +done + +echo "Computed all versions:" +cat list_versions.md + +cd .. + +echo "Local docs update finished." + + + diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..631416c --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "keypop-calypso-certificate-java-api" \ No newline at end of file diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java new file mode 100644 index 0000000..acead4c --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java @@ -0,0 +1,64 @@ +/* ****************************************************************************** + * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + ****************************************************************************** */ +package org.eclipse.keypop.calypso.certificate; + +import org.eclipse.keypop.calypso.certificate.ca.CaCertificateManager; +import org.eclipse.keypop.calypso.certificate.ca.CaCertificateSettings; +import org.eclipse.keypop.calypso.certificate.card.CardCertificateManager; +import org.eclipse.keypop.calypso.certificate.card.CardCertificateSettings; + +/** + * Factory of {@link CaCertificateSettings}, {@link CaCertificateManager}, {@link + * CardCertificateSettings} and {@link CardCertificateManager}. + * + * @since 0.1.0 + */ +public interface CalypsoCertificateApiFactory { + + /** + * Returns a new instance of the specified CA certificate settings class. + * + * @param The type of the lowest level child object. + * @param classOfT The `Class` object of the desired CA certificate settings type. This type must + * extend {@link CaCertificateSettings}. + * @return A non-null reference. + * @since 0.1.0 + */ + T createCaCertificateSettings(Class classOfT); + + /** + * Returns a new instance of the specified card certificate settings class. + * + * @param The type of the lowest level child object. + * @param classOfT The `Class` object of the desired card certificate settings type. This type + * must extend {@link CardCertificateSettings}. + * @return A non-null reference. + * @since 0.1.0 + */ + T createCardCertificateSettings(Class classOfT); + + /** + * Returns a new instance of {@link CaCertificateManager}. + * + * @param settings The CA certificate settings to use. + * @return A non-null reference. + * @since 0.1.0 + */ + CaCertificateManager createCaCertificateManager(CaCertificateSettings settings); + + /** + * Returns a new instance of {@link CardCertificateManager}. + * + * @param settings The card certificate settings to use. + * @return A non-null reference. + * @since 0.1.0 + */ + CardCertificateManager createCardCertificateManager(CardCertificateSettings settings); +} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiProperties.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiProperties.java new file mode 100644 index 0000000..52bf030 --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiProperties.java @@ -0,0 +1,28 @@ +/* ****************************************************************************** + * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + ****************************************************************************** */ +package org.eclipse.keypop.calypso.certificate; + +/** + * API properties. + * + * @since 0.1.0 + */ +public final class CalypsoCertificateApiProperties { + + /** + * API version: {@value} + * + * @since 0.1.0 + */ + public static final String VERSION = "0.1"; + + /** Private constructor */ + private CalypsoCertificateApiProperties() {} +} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateManager.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateManager.java new file mode 100644 index 0000000..bb0d729 --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateManager.java @@ -0,0 +1,25 @@ +/* ****************************************************************************** + * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + ****************************************************************************** */ +package org.eclipse.keypop.calypso.certificate.ca; + +/** + * Provides a method to create a CA certificate. + * + * @since 0.1.0 + */ +public interface CaCertificateManager { + /** + * Returns a byte array containing the data to be stored in a card. + * + * @return A 384-byte byte array. + * @since 0.1.0 + */ + byte[] createCertificate(); +} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateSettings.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateSettings.java new file mode 100644 index 0000000..4f9849d --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateSettings.java @@ -0,0 +1,18 @@ +/* ****************************************************************************** + * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + ****************************************************************************** */ +package org.eclipse.keypop.calypso.certificate.ca; + +/** + * Settings for a CA certificate. It is used in conjunction with the CaCertificateManager class for + * managing and creating certificates . + * + * @since 0.1.0 + */ +public interface CaCertificateSettings {} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateSettingsV1.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateSettingsV1.java new file mode 100644 index 0000000..c701f21 --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateSettingsV1.java @@ -0,0 +1,206 @@ +/* ****************************************************************************** + * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + ****************************************************************************** */ +package org.eclipse.keypop.calypso.certificate.ca; + +import java.security.PrivateKey; +import java.security.PublicKey; +import org.eclipse.keypop.calypso.certificate.ca.spi.CaCertificateSignerSpi; + +/** + * Extends {@link CaCertificateSettings} to manage and generate CA certificates, conforming to + * version 1 of the CA certificate format. + * + * @since 0.1.0 + */ +public interface CaCertificateSettingsV1 extends CaCertificateSettings { + /** + * Sets the external signer to be used for generating signed CA certificates. + * + * @param caCertificateSigner The external signer for ca certificate generation. + * @return The current instance. + * @throws IllegalArgumentException If the provided signer is null, invalid, or not compatible + * with the required signature formats. + * @throws IllegalStateException If an internal signer has already been configured using {@link + * #useInternalSigner(PrivateKey, byte[])}. + * @since 0.1.0 + */ + CaCertificateSettingsV1 useExternalSigner(CaCertificateSignerSpi caCertificateSigner); + + /** + * Configures the settings to use the internal signer for generating signed CA certificates. + * + *

The internal signer will use the provided 2048 bits RSA private key with a public exponent + * of 65537 and the specified public key reference for signing operations. + * + * @param issuerPrivateKey The RSA private key of the issuer (2048 bits, public exponent 65537). + * @param issuerPublicKeyReference A 29-byte byte array representing a reference to the issuer's + * public key. + * @return The current instance. + * @throws IllegalArgumentException If any of the provided arguments are null, invalid, or have + * incompatible formats. + * @throws IllegalStateException If an external signer has already been set using {@link + * #useExternalSigner(CaCertificateSignerSpi)}. + * @since 0.1.0 + */ + CaCertificateSettingsV1 useInternalSigner( + PrivateKey issuerPrivateKey, byte[] issuerPublicKeyReference); + + /** + * Sets the public key of the CA, provided as a 64-byte raw array. + * + *

This key is expected to be a 2048 bits RSA public key with a public exponent equal to 65537. + * It will be used for the verification of card certificates. + * + *

The associated reference is a 29-byte byte array. + * + * @param caPublicKey The RSA public key of the CA (2048 bits, public exponent 65537). + * @param caPublicKeyReference The CA public key reference. + * @return The current instance. + * @throws IllegalArgumentException If one of the provided argument is null or invalid. + * @since 0.1.0 + */ + CaCertificateSettingsV1 setCaPublicKey(PublicKey caPublicKey, byte[] caPublicKeyReference); + + /** + * Sets the validity period of the certificate's public key. This defines the timeframe when the + * certificate can be considered trusted. + * + *

If neither start nor end date is set, the certificate will have open-ended validity. + * + * @param startDateYear The year of the start date (e.g., 2024). Valid range: 1900-2100. + * @param startDateMonth The month of the start date (1-12). + * @param startDateDay The day of the start date (1-31). + * @param endDateYear The year of the end date (e.g., 2025). Valid range: 1900-2100. + * @param endDateMonth The month of the end date (1-12). + * @param endDateDay The day of the end date. + * @return The current instance. + * @throws IllegalArgumentException If any date parameter is out of range. + * @since 0.1.0 + */ + CaCertificateSettingsV1 setValidityPeriod( + int startDateYear, + int startDateMonth, + int startDateDay, + int endDateYear, + int endDateMonth, + int endDateDay); + + /** + * Sets the AID (Application Identifier) of the card certificates for which the certificate is + * applicable. + * + *

If the AID is not set, the certificate will be applicable to any card certificates. + * + * @param aid The AID value as a 5 to 16 bytes byte array. + * @return The current instance. + * @throws IllegalArgumentException If the provided argument is null or out of range. + * @since 0.1.0 + */ + CaCertificateSettingsV1 setAid(byte[] aid); + + /** + * Sets the CA rights for this card certificate, controlling which types of certificates the card + * can be used to authenticate. + * + *

The provided caRights byte defines the following permissions: + * + *

    + *
  • Bits b7-b4: Reserved for future use (RFU). Must be set to 0.
    + * Attempting to set non-zero values in these bits will throw an {@link + * IllegalArgumentException}. + *
  • Bits b3-b2: Card key certificates authentication right: + *
      + *
    • %00: CardCert authentication right not specified. + *
    • %01: CardCert authentication forbidden. + *
    • %10: CardCert authentication allowed. + *
    • %11: Reserved for future use. + *
    + *
  • Bits b1-b0: CA key certificates authentication right: + *
      + *
    • %00: CACert authentication right not specified. + *
    • %01: CACert authentication forbidden. + *
    • %10: CACert authentication allowed. + *
    • %11: Reserved for future use. + *
    + *
+ * + * @param caRights The byte representing the CA rights for this card certificate. + * @return The current instance. + * @throws IllegalArgumentException If the provided byte contains invalid values, including + * non-zero values in reserved bits. + * @since 0.1.0 + */ + CaCertificateSettingsV1 setCaRights(byte caRights); + + /** + * Sets the CA scope for this card certificate, defining the context in which the CA key pair can + * be used. + * + *

The provided caScope byte specifies the allowed usage context: + * + *

    + *
  • %00: Scope restrictions not specified. + *
  • %01: Allowed only for development, tests, pilots, etc. (limited scope). + *
  • %FF: No scope restriction (full scope). + *
  • Other values: Reserved for future use (RFU). + *
+ * + * Choosing an appropriate scope is crucial for security and proper certificate management. Select + * a scope that aligns with the intended use of the CA key pair to avoid potential misuse. + * + * @param caScope The byte representing the CA scope for this card certificate. + * @return The current instance. + * @throws IllegalArgumentException If the provided byte contains an invalid value, including + * non-standard or reserved values. + * @since 0.1.0 + */ + CaCertificateSettingsV1 setCaScope(byte caScope); + + /** + * Sets the CA operating mode, controlling how the target Calypso Prime PKI application AID should + * be verified during card certificate validation. + * + *

The provided caOperatingMode byte defines the following behavior: + * + *

    + *
  • Bits b7-b1: Reserved for future use (RFU). Must be set to 0.
    + * Attempting to set non-zero values in these bits will throw an {@link + * IllegalArgumentException}. + *
  • Bit b0: Target Calypso Prime PKI application AID matching: + *
      + *
    • %0: Truncation forbidden: + *
        + *
      • The size of the card AID in the card certificate must be equal to the size of + * the target AID specified in this certificate. + *
      • The corresponding number of first (leftmost) bytes of the card AID value in + * the card certificate and the target AID value in this certificate must be + * equal. + *
      + *
    • %1: Truncation allowed: + *
        + *
      • The size of the card AID in the card certificate can be equal to or greater + * than the size of the target AID specified in this certificate. + *
      • The specified size first (leftmost) bytes of the card AID value in the card + * certificate and the target AID value in this certificate must be equal. + *
      + *
    + *
+ * + * Choosing an appropriate operating mode is crucial for secure and verified certificate issuance. + * Refer to the Calypso Prime PKI specifications for further details and guidelines. + * + * @param caOperatingMode The byte representing the CA operating mode for this card certificate. + * @return The current instance. + * @throws IllegalArgumentException If the provided byte contains invalid values, including + * non-zero values in reserved bits. + * @since 0.1.0 + */ + CaCertificateSettingsV1 setCaOperatingMode(byte caOperatingMode); +} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/package-info.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/package-info.java new file mode 100644 index 0000000..887d03c --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/package-info.java @@ -0,0 +1,6 @@ +/** + * Calypso Certification Authority certificates management. + * + * @since 0.2.0 + */ +package org.eclipse.keypop.calypso.certificate.ca; diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/CaCertificateSignerSpi.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/CaCertificateSignerSpi.java new file mode 100644 index 0000000..99f2434 --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/CaCertificateSignerSpi.java @@ -0,0 +1,54 @@ +/* ****************************************************************************** + * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + ****************************************************************************** */ +package org.eclipse.keypop.calypso.certificate.ca.spi; + +/** + * Signer for a Calypso CA certificate. + * + *

Implementations of this interface provide the cryptographic signing functionality used to + * generate signed CA certificates. + * + * @since 0.1.0 + */ +public interface CaCertificateSignerSpi { + + /** + * Gets the reference to the issuer's public key. + * + * @return A 29-byte byte array. + * @since 0.1.0 + */ + byte[] getIssuerPublicKeyReference(); + + /** + * Generates a signed CA certificate based on the provided data. + * + * @param allData The byte array containing all the data to be included in the certificate. This + * typically includes information about the subject, validity period, and other certificate + * attributes. + * @return The signed card certificate, represented as a byte array. + * @since 0.1.0 + */ + byte[] generateSignedCertificate(byte[] allData); + + /** + * Generates a signed CA certificate based on the provided data and recoverable data. This method + * is similar to {@link #generateSignedCertificate(byte[])}, but allows for separate handling of + * non-recoverable and recoverable data during signing. + * + * @param data The byte array containing the non-recoverable data for the certificate. + * @param recoverableData The byte array containing the recoverable data for the certificate. This + * might be encrypted or protected data that shouldn't be included in the final certificate + * but is needed for signature generation. + * @return The signed card certificate, represented as a byte array. + * @since 0.1.0 + */ + byte[] generateSignedCertificate(byte[] data, byte[] recoverableData); +} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/package-info.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/package-info.java new file mode 100644 index 0000000..08349ec --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/package-info.java @@ -0,0 +1,6 @@ +/** + * Calypso Certification Authority certificates external signing. + * + * @since 0.2.0 + */ +package org.eclipse.keypop.calypso.certificate.ca.spi; diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateManager.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateManager.java new file mode 100644 index 0000000..7b3b2d4 --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateManager.java @@ -0,0 +1,34 @@ +/* ****************************************************************************** + * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + ****************************************************************************** */ +package org.eclipse.keypop.calypso.certificate.card; + +/** + * Provides methods to create a card certificate and get the associated card public key. + * + * @since 0.1.0 + */ +public interface CardCertificateManager { + + /** + * Based on provided settings, generates the certificate data to be stored in a card. + * + * @return A 384-byte byte array. + * @since 0.1.0 + */ + byte[] createCertificate(); + + /** + * Based on provided settings, extracts the public key data. + * + * @return A 64-byte byte array. + * @since 0.1.0 + */ + byte[] getCardPublicKeyData(); +} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateSettings.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateSettings.java new file mode 100644 index 0000000..8271e15 --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateSettings.java @@ -0,0 +1,18 @@ +/* ****************************************************************************** + * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + ****************************************************************************** */ +package org.eclipse.keypop.calypso.certificate.card; + +/** + * Settings for a card certificate. It is used in conjunction with the CaCertificateManager class + * for managing and creating certificates. + * + * @since 0.1.0 + */ +public interface CardCertificateSettings {} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateSettingsV1.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateSettingsV1.java new file mode 100644 index 0000000..8e888ed --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateSettingsV1.java @@ -0,0 +1,133 @@ +/* ****************************************************************************** + * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + ****************************************************************************** */ +package org.eclipse.keypop.calypso.certificate.card; + +import java.security.PrivateKey; +import org.eclipse.keypop.calypso.certificate.card.spi.CardCertificateSignerSpi; + +/** + * Extends {@link CardCertificateSettings} to manage and generate card certificates, conforming to + * version 1 of the card certificate format. + * + * @since 0.1.0 + */ +public interface CardCertificateSettingsV1 extends CardCertificateSettings { + + /** + * Sets the external signer to be used for generating signed card certificates. + * + * @param cardCertificateSigner The external signer for card certificate generation. + * @return The current instance. + * @throws IllegalArgumentException If the provided signer is null, invalid, or not compatible + * with the required signature formats. + * @throws IllegalStateException If an internal signer has already been configured using {@link + * #useInternalSigner(PrivateKey, byte[])}. + * @since 0.1.0 + */ + CardCertificateSettingsV1 useExternalSigner(CardCertificateSignerSpi cardCertificateSigner); + + /** + * Configures the settings to use the internal signer for generating signed card certificates. + * + *

The internal signer will use the provided 2048 bits RSA private key with a public exponent + * of 65537 and the specified public key reference for signing operations. + * + * @param issuerPrivateKey The RSA private key of the issuer (2048 bits, public exponent 65537). + * @param issuerPublicKeyReference A 29-byte byte array representing a reference to the issuer's + * public key. + * @return The current instance. + * @throws IllegalArgumentException If any of the provided arguments are null, invalid, or have + * incompatible formats. + * @throws IllegalStateException If an external signer has already been set using {@link + * #useExternalSigner(CardCertificateSignerSpi)}. + * @since 0.1.0 + */ + CardCertificateSettingsV1 useInternalSigner( + PrivateKey issuerPrivateKey, byte[] issuerPublicKeyReference); + + /** + * Sets the public key of the card, provided as a 64-byte raw array. + * + *

This key is expected to be on the secp256r1 elliptic curve. It will be used + * for the verification of card signatures. + * + * @param cardPublicKey The 64-byte raw array representing the public key on the + * secp256r1 curve. + * @return The current instance. + * @throws IllegalArgumentException If the provided key is null or has an invalid length (not 64). + * @since 0.1.0 + */ + CardCertificateSettingsV1 setCardPublicKey(byte[] cardPublicKey); + + /** + * Sets the validity period of the certificate's public key. This defines the timeframe when the + * certificate can be considered trusted. + * + *

If neither start nor end date is set, the certificate will have open-ended validity. + * + * @param startDateYear The year of the start date (e.g., 2024). Valid range: 1900-2100. + * @param startDateMonth The month of the start date (1-12). + * @param startDateDay The day of the start date (1-31). + * @param endDateYear The year of the end date (e.g., 2025). Valid range: 1900-2100. + * @param endDateMonth The month of the end date (1-12). + * @param endDateDay The day of the end date. + * @return The current instance. + * @throws IllegalArgumentException If any date parameter is out of range. + * @since 0.1.0 + */ + CardCertificateSettingsV1 setValidityPeriod( + int startDateYear, + int startDateMonth, + int startDateDay, + int endDateYear, + int endDateMonth, + int endDateDay); + + /** + * Sets the AID (Application Identifier) associated with the certificate. + * + * @param aid The AID value as a 5 to 16 bytes byte array. + * @return The current instance. + * @throws IllegalArgumentException If the provided argument is null or out of range. + * @since 0.1.0 + */ + CardCertificateSettingsV1 setAid(byte[] aid); + + /** + * Sets the serial number of the card for which the certificate is being generated. + * + * @param serialNumber The serial number of the card as a 8-byte byte array. + * @return The current instance. + * @throws IllegalArgumentException If the provided argument is null or out of range. + * @since 0.1.0 + */ + CardCertificateSettingsV1 setCardSerialNumber(byte[] serialNumber); + + /** + * Sets the startup info for the card certificate. + * + * @param startupInfo The 7-byte byte array representing the startup info for the card + * certificate. + * @return The current instance. + * @throws IllegalArgumentException If the provided argument is null or out of range. + * @since 0.1.0 + */ + CardCertificateSettingsV1 setCardStartupInfo(byte[] startupInfo); + + /** + * Sets the index used to differentiate two card certificates generated with the same issuer + * public key reference for the same card. + * + * @param index The index of the card certificate. + * @return The current instance. + * @since 0.1.0 + */ + CardCertificateSettingsV1 setIndex(int index); +} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/package-info.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/package-info.java new file mode 100644 index 0000000..5f785db --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/package-info.java @@ -0,0 +1,6 @@ +/** + * Calypso card certificates management. + * + * @since 0.2.0 + */ +package org.eclipse.keypop.calypso.certificate.card; diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/CardCertificateSignerSpi.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/CardCertificateSignerSpi.java new file mode 100644 index 0000000..22faf74 --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/CardCertificateSignerSpi.java @@ -0,0 +1,54 @@ +/* ****************************************************************************** + * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + ****************************************************************************** */ +package org.eclipse.keypop.calypso.certificate.card.spi; + +/** + * Signer for a Calypso card certificate. + * + *

Implementations of this interface provide the cryptographic signing functionality used to + * generate signed card certificates. + * + * @since 0.1.0 + */ +public interface CardCertificateSignerSpi { + + /** + * Gets the reference to the issuer's public key. + * + * @return A 29-byte byte array. + * @since 0.1.0 + */ + byte[] getIssuerPublicKeyReference(); + + /** + * Generates a signed card certificate based on the provided data. + * + * @param allData The byte array containing all the data to be included in the certificate. This + * typically includes information about the subject, validity period, and other certificate + * attributes. + * @return The signed card certificate, represented as a byte array. + * @since 0.1.0 + */ + byte[] generateSignedCertificate(byte[] allData); + + /** + * Generates a signed card certificate based on the provided data and recoverable data. This + * method is similar to {@link #generateSignedCertificate(byte[])}, but allows for separate + * handling of non-recoverable and recoverable data during signing. + * + * @param data The byte array containing the non-recoverable data for the certificate. + * @param recoverableData The byte array containing the recoverable data for the certificate. This + * might be encrypted or protected data that shouldn't be included in the final certificate + * but is needed for signature generation. + * @return The signed card certificate, represented as a byte array. + * @since 0.1.0 + */ + byte[] generateSignedCertificate(byte[] data, byte[] recoverableData); +} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/package-info.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/package-info.java new file mode 100644 index 0000000..cd85713 --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/package-info.java @@ -0,0 +1,6 @@ +/** + * Calypso card certificate external signing. + * + * @since 0.2.0 + */ +package org.eclipse.keypop.calypso.certificate.card.spi; diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/package-info.java b/src/main/java/org/eclipse/keypop/calypso/certificate/package-info.java new file mode 100644 index 0000000..ce0d140 --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/package-info.java @@ -0,0 +1,6 @@ +/** + * Calypso Certification Authority and card certificates management. + * + * @since 0.2.0 + */ +package org.eclipse.keypop.calypso.certificate; diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html new file mode 100644 index 0000000..bc50404 --- /dev/null +++ b/src/main/javadoc/overview.html @@ -0,0 +1,15 @@ + + +

+ Welcome to the Java documentation of the Keypop Calypso Certificate Java API, + part of the Keypop open-source project hosted by the + Eclipse Foundation. +

+

+ Aligned with the Terminal Calypso Certificate UML specifications proposed by + the Calypso Networks Association (available + here), + it defines the generic interfaces required to create Calypso certificates. +

+ + \ No newline at end of file diff --git a/src/test/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiPropertiesTest.java b/src/test/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiPropertiesTest.java new file mode 100644 index 0000000..bb339d2 --- /dev/null +++ b/src/test/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiPropertiesTest.java @@ -0,0 +1,42 @@ +/* ****************************************************************************** + * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + ****************************************************************************** */ +package org.eclipse.keypop.calypso.certificate; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.Properties; +import org.junit.BeforeClass; +import org.junit.Test; + +public class CalypsoCertificateApiPropertiesTest { + + private static String libVersion; + + @BeforeClass + public static void beforeClass() throws Exception { + InputStream inputStream = new FileInputStream("gradle.properties"); + try { + Properties properties = new Properties(); + properties.load(inputStream); + libVersion = properties.getProperty("version"); + } finally { + inputStream.close(); + } + } + + @Test + public void versionIsCorrectlyWritten() { + String apiVersion = CalypsoCertificateApiProperties.VERSION; + assertThat(apiVersion).matches("\\d+\\.\\d+"); + assertThat(libVersion).startsWith(apiVersion); + } +} From 3a2c30eb0384efa60595f7aae184206fbb15c4f8 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Wed, 14 Feb 2024 15:11:52 +0100 Subject: [PATCH 02/13] refactor: update certificates builder --- .../CalypsoCertificateApiFactory.java | 41 ++++--------------- .../certificate/ca/CaCertificateSettings.java | 18 -------- ...nager.java => CalypsoCaCertificateV1.java} | 9 ++-- ...ava => CalypsoCaCertificateV1Builder.java} | 33 +++++++++------ ...ava => CalypsoCaCertificateSignerSpi.java} | 4 +- ...ngs.java => CalypsoCardCertificateV1.java} | 14 +++++-- ...a => CalypsoCardCertificateV1Builder.java} | 33 +++++++++------ .../card/CardCertificateManager.java | 34 --------------- ...a => CalypsoCardCertificateSignerSpi.java} | 4 +- 9 files changed, 67 insertions(+), 123 deletions(-) delete mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateSettings.java rename src/main/java/org/eclipse/keypop/calypso/certificate/ca/{CaCertificateManager.java => CalypsoCaCertificateV1.java} (75%) rename src/main/java/org/eclipse/keypop/calypso/certificate/ca/{CaCertificateSettingsV1.java => CalypsoCaCertificateV1Builder.java} (90%) rename src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/{CaCertificateSignerSpi.java => CalypsoCaCertificateSignerSpi.java} (95%) rename src/main/java/org/eclipse/keypop/calypso/certificate/card/{CardCertificateSettings.java => CalypsoCardCertificateV1.java} (66%) rename src/main/java/org/eclipse/keypop/calypso/certificate/card/{CardCertificateSettingsV1.java => CalypsoCardCertificateV1Builder.java} (83%) delete mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateManager.java rename src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/{CardCertificateSignerSpi.java => CalypsoCardCertificateSignerSpi.java} (95%) diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java index acead4c..f7e03b4 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java @@ -9,56 +9,29 @@ ****************************************************************************** */ package org.eclipse.keypop.calypso.certificate; -import org.eclipse.keypop.calypso.certificate.ca.CaCertificateManager; -import org.eclipse.keypop.calypso.certificate.ca.CaCertificateSettings; -import org.eclipse.keypop.calypso.certificate.card.CardCertificateManager; -import org.eclipse.keypop.calypso.certificate.card.CardCertificateSettings; +import org.eclipse.keypop.calypso.certificate.ca.CalypsoCaCertificateV1Builder; +import org.eclipse.keypop.calypso.certificate.card.CalypsoCardCertificateV1Builder; /** - * Factory of {@link CaCertificateSettings}, {@link CaCertificateManager}, {@link - * CardCertificateSettings} and {@link CardCertificateManager}. + * Factory of CA and card certificate builders. * * @since 0.1.0 */ public interface CalypsoCertificateApiFactory { /** - * Returns a new instance of the specified CA certificate settings class. + * Returns a builder of Calypso CA certificate version 1. * - * @param The type of the lowest level child object. - * @param classOfT The `Class` object of the desired CA certificate settings type. This type must - * extend {@link CaCertificateSettings}. * @return A non-null reference. * @since 0.1.0 */ - T createCaCertificateSettings(Class classOfT); + CalypsoCaCertificateV1Builder createCalypsoCaCertificateV1Builder(); /** - * Returns a new instance of the specified card certificate settings class. + * Returns a builder of Calypso card certificate version 1. * - * @param The type of the lowest level child object. - * @param classOfT The `Class` object of the desired card certificate settings type. This type - * must extend {@link CardCertificateSettings}. * @return A non-null reference. * @since 0.1.0 */ - T createCardCertificateSettings(Class classOfT); - - /** - * Returns a new instance of {@link CaCertificateManager}. - * - * @param settings The CA certificate settings to use. - * @return A non-null reference. - * @since 0.1.0 - */ - CaCertificateManager createCaCertificateManager(CaCertificateSettings settings); - - /** - * Returns a new instance of {@link CardCertificateManager}. - * - * @param settings The card certificate settings to use. - * @return A non-null reference. - * @since 0.1.0 - */ - CardCertificateManager createCardCertificateManager(CardCertificateSettings settings); + CalypsoCardCertificateV1Builder createCalypsoCardCertificateV1Builder(); } diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateSettings.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateSettings.java deleted file mode 100644 index 4f9849d..0000000 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateSettings.java +++ /dev/null @@ -1,18 +0,0 @@ -/* ****************************************************************************** - * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ - * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. - * - * SPDX-License-Identifier: MIT - ****************************************************************************** */ -package org.eclipse.keypop.calypso.certificate.ca; - -/** - * Settings for a CA certificate. It is used in conjunction with the CaCertificateManager class for - * managing and creating certificates . - * - * @since 0.1.0 - */ -public interface CaCertificateSettings {} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateManager.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java similarity index 75% rename from src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateManager.java rename to src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java index bb0d729..2a24b2d 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateManager.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java @@ -10,16 +10,17 @@ package org.eclipse.keypop.calypso.certificate.ca; /** - * Provides a method to create a CA certificate. + * A Calypso CA certificate version 1. * * @since 0.1.0 */ -public interface CaCertificateManager { +public interface CalypsoCaCertificateV1 { + /** - * Returns a byte array containing the data to be stored in a card. + * Returns a byte array corresponding to the certificate as it is stored in the card. * * @return A 384-byte byte array. * @since 0.1.0 */ - byte[] createCertificate(); + byte[] getRawData(); } diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateSettingsV1.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java similarity index 90% rename from src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateSettingsV1.java rename to src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java index c701f21..b6a6da2 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CaCertificateSettingsV1.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java @@ -11,15 +11,14 @@ import java.security.PrivateKey; import java.security.PublicKey; -import org.eclipse.keypop.calypso.certificate.ca.spi.CaCertificateSignerSpi; +import org.eclipse.keypop.calypso.certificate.ca.spi.CalypsoCaCertificateSignerSpi; /** - * Extends {@link CaCertificateSettings} to manage and generate CA certificates, conforming to - * version 1 of the CA certificate format. + * Builds CA certificates conforming to version 1 of the Calypso CA certificate format. * * @since 0.1.0 */ -public interface CaCertificateSettingsV1 extends CaCertificateSettings { +public interface CalypsoCaCertificateV1Builder { /** * Sets the external signer to be used for generating signed CA certificates. * @@ -31,7 +30,8 @@ public interface CaCertificateSettingsV1 extends CaCertificateSettings { * #useInternalSigner(PrivateKey, byte[])}. * @since 0.1.0 */ - CaCertificateSettingsV1 useExternalSigner(CaCertificateSignerSpi caCertificateSigner); + CalypsoCaCertificateV1Builder useExternalSigner( + CalypsoCaCertificateSignerSpi caCertificateSigner); /** * Configures the settings to use the internal signer for generating signed CA certificates. @@ -46,10 +46,10 @@ public interface CaCertificateSettingsV1 extends CaCertificateSettings { * @throws IllegalArgumentException If any of the provided arguments are null, invalid, or have * incompatible formats. * @throws IllegalStateException If an external signer has already been set using {@link - * #useExternalSigner(CaCertificateSignerSpi)}. + * #useExternalSigner(CalypsoCaCertificateSignerSpi)}. * @since 0.1.0 */ - CaCertificateSettingsV1 useInternalSigner( + CalypsoCaCertificateV1Builder useInternalSigner( PrivateKey issuerPrivateKey, byte[] issuerPublicKeyReference); /** @@ -66,7 +66,7 @@ CaCertificateSettingsV1 useInternalSigner( * @throws IllegalArgumentException If one of the provided argument is null or invalid. * @since 0.1.0 */ - CaCertificateSettingsV1 setCaPublicKey(PublicKey caPublicKey, byte[] caPublicKeyReference); + CalypsoCaCertificateV1Builder setCaPublicKey(PublicKey caPublicKey, byte[] caPublicKeyReference); /** * Sets the validity period of the certificate's public key. This defines the timeframe when the @@ -84,7 +84,7 @@ CaCertificateSettingsV1 useInternalSigner( * @throws IllegalArgumentException If any date parameter is out of range. * @since 0.1.0 */ - CaCertificateSettingsV1 setValidityPeriod( + CalypsoCaCertificateV1Builder setValidityPeriod( int startDateYear, int startDateMonth, int startDateDay, @@ -103,7 +103,7 @@ CaCertificateSettingsV1 setValidityPeriod( * @throws IllegalArgumentException If the provided argument is null or out of range. * @since 0.1.0 */ - CaCertificateSettingsV1 setAid(byte[] aid); + CalypsoCaCertificateV1Builder setAid(byte[] aid); /** * Sets the CA rights for this card certificate, controlling which types of certificates the card @@ -137,7 +137,7 @@ CaCertificateSettingsV1 setValidityPeriod( * non-zero values in reserved bits. * @since 0.1.0 */ - CaCertificateSettingsV1 setCaRights(byte caRights); + CalypsoCaCertificateV1Builder setCaRights(byte caRights); /** * Sets the CA scope for this card certificate, defining the context in which the CA key pair can @@ -161,7 +161,7 @@ CaCertificateSettingsV1 setValidityPeriod( * non-standard or reserved values. * @since 0.1.0 */ - CaCertificateSettingsV1 setCaScope(byte caScope); + CalypsoCaCertificateV1Builder setCaScope(byte caScope); /** * Sets the CA operating mode, controlling how the target Calypso Prime PKI application AID should @@ -202,5 +202,12 @@ CaCertificateSettingsV1 setValidityPeriod( * non-zero values in reserved bits. * @since 0.1.0 */ - CaCertificateSettingsV1 setCaOperatingMode(byte caOperatingMode); + CalypsoCaCertificateV1Builder setCaOperatingMode(byte caOperatingMode); + + /** + * TODO + * + * @return + */ + CalypsoCaCertificateV1 build(); } diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/CaCertificateSignerSpi.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/CalypsoCaCertificateSignerSpi.java similarity index 95% rename from src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/CaCertificateSignerSpi.java rename to src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/CalypsoCaCertificateSignerSpi.java index 99f2434..dd704bc 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/CaCertificateSignerSpi.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/CalypsoCaCertificateSignerSpi.java @@ -13,11 +13,11 @@ * Signer for a Calypso CA certificate. * *

Implementations of this interface provide the cryptographic signing functionality used to - * generate signed CA certificates. + * generate signed Calypso CA certificates. * * @since 0.1.0 */ -public interface CaCertificateSignerSpi { +public interface CalypsoCaCertificateSignerSpi { /** * Gets the reference to the issuer's public key. diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateSettings.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java similarity index 66% rename from src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateSettings.java rename to src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java index 8271e15..84ac954 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateSettings.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java @@ -10,9 +10,17 @@ package org.eclipse.keypop.calypso.certificate.card; /** - * Settings for a card certificate. It is used in conjunction with the CaCertificateManager class - * for managing and creating certificates. + * A Calypso card certificate version 1. * * @since 0.1.0 */ -public interface CardCertificateSettings {} +public interface CalypsoCardCertificateV1 { + + /** + * Returns a byte array corresponding to the certificate as it is stored in the card. + * + * @return A 316-byte byte array. + * @since 0.1.0 + */ + byte[] getRawData(); +} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateSettingsV1.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java similarity index 83% rename from src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateSettingsV1.java rename to src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java index 8e888ed..b487961 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateSettingsV1.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java @@ -10,15 +10,14 @@ package org.eclipse.keypop.calypso.certificate.card; import java.security.PrivateKey; -import org.eclipse.keypop.calypso.certificate.card.spi.CardCertificateSignerSpi; +import org.eclipse.keypop.calypso.certificate.card.spi.CalypsoCardCertificateSignerSpi; /** - * Extends {@link CardCertificateSettings} to manage and generate card certificates, conforming to - * version 1 of the card certificate format. + * Builds a card certificate conforming to version 1 of the Calypso card certificate format. * * @since 0.1.0 */ -public interface CardCertificateSettingsV1 extends CardCertificateSettings { +public interface CalypsoCardCertificateV1Builder { /** * Sets the external signer to be used for generating signed card certificates. @@ -31,7 +30,8 @@ public interface CardCertificateSettingsV1 extends CardCertificateSettings { * #useInternalSigner(PrivateKey, byte[])}. * @since 0.1.0 */ - CardCertificateSettingsV1 useExternalSigner(CardCertificateSignerSpi cardCertificateSigner); + CalypsoCardCertificateV1Builder useExternalSigner( + CalypsoCardCertificateSignerSpi cardCertificateSigner); /** * Configures the settings to use the internal signer for generating signed card certificates. @@ -46,10 +46,10 @@ public interface CardCertificateSettingsV1 extends CardCertificateSettings { * @throws IllegalArgumentException If any of the provided arguments are null, invalid, or have * incompatible formats. * @throws IllegalStateException If an external signer has already been set using {@link - * #useExternalSigner(CardCertificateSignerSpi)}. + * #useExternalSigner(CalypsoCardCertificateSignerSpi)}. * @since 0.1.0 */ - CardCertificateSettingsV1 useInternalSigner( + CalypsoCardCertificateV1Builder useInternalSigner( PrivateKey issuerPrivateKey, byte[] issuerPublicKeyReference); /** @@ -64,7 +64,7 @@ CardCertificateSettingsV1 useInternalSigner( * @throws IllegalArgumentException If the provided key is null or has an invalid length (not 64). * @since 0.1.0 */ - CardCertificateSettingsV1 setCardPublicKey(byte[] cardPublicKey); + CalypsoCardCertificateV1Builder setCardPublicKey(byte[] cardPublicKey); /** * Sets the validity period of the certificate's public key. This defines the timeframe when the @@ -82,7 +82,7 @@ CardCertificateSettingsV1 useInternalSigner( * @throws IllegalArgumentException If any date parameter is out of range. * @since 0.1.0 */ - CardCertificateSettingsV1 setValidityPeriod( + CalypsoCardCertificateV1Builder setValidityPeriod( int startDateYear, int startDateMonth, int startDateDay, @@ -98,7 +98,7 @@ CardCertificateSettingsV1 setValidityPeriod( * @throws IllegalArgumentException If the provided argument is null or out of range. * @since 0.1.0 */ - CardCertificateSettingsV1 setAid(byte[] aid); + CalypsoCardCertificateV1Builder setAid(byte[] aid); /** * Sets the serial number of the card for which the certificate is being generated. @@ -108,7 +108,7 @@ CardCertificateSettingsV1 setValidityPeriod( * @throws IllegalArgumentException If the provided argument is null or out of range. * @since 0.1.0 */ - CardCertificateSettingsV1 setCardSerialNumber(byte[] serialNumber); + CalypsoCardCertificateV1Builder setCardSerialNumber(byte[] serialNumber); /** * Sets the startup info for the card certificate. @@ -119,7 +119,7 @@ CardCertificateSettingsV1 setValidityPeriod( * @throws IllegalArgumentException If the provided argument is null or out of range. * @since 0.1.0 */ - CardCertificateSettingsV1 setCardStartupInfo(byte[] startupInfo); + CalypsoCardCertificateV1Builder setCardStartupInfo(byte[] startupInfo); /** * Sets the index used to differentiate two card certificates generated with the same issuer @@ -129,5 +129,12 @@ CardCertificateSettingsV1 setValidityPeriod( * @return The current instance. * @since 0.1.0 */ - CardCertificateSettingsV1 setIndex(int index); + CalypsoCardCertificateV1Builder setIndex(int index); + + /** + * TODO + * + * @return + */ + CalypsoCardCertificateV1 build(); } diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateManager.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateManager.java deleted file mode 100644 index 7b3b2d4..0000000 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CardCertificateManager.java +++ /dev/null @@ -1,34 +0,0 @@ -/* ****************************************************************************** - * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ - * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. - * - * SPDX-License-Identifier: MIT - ****************************************************************************** */ -package org.eclipse.keypop.calypso.certificate.card; - -/** - * Provides methods to create a card certificate and get the associated card public key. - * - * @since 0.1.0 - */ -public interface CardCertificateManager { - - /** - * Based on provided settings, generates the certificate data to be stored in a card. - * - * @return A 384-byte byte array. - * @since 0.1.0 - */ - byte[] createCertificate(); - - /** - * Based on provided settings, extracts the public key data. - * - * @return A 64-byte byte array. - * @since 0.1.0 - */ - byte[] getCardPublicKeyData(); -} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/CardCertificateSignerSpi.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/CalypsoCardCertificateSignerSpi.java similarity index 95% rename from src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/CardCertificateSignerSpi.java rename to src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/CalypsoCardCertificateSignerSpi.java index 22faf74..5aa6797 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/CardCertificateSignerSpi.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/CalypsoCardCertificateSignerSpi.java @@ -13,11 +13,11 @@ * Signer for a Calypso card certificate. * *

Implementations of this interface provide the cryptographic signing functionality used to - * generate signed card certificates. + * generate signed Calypso card certificates. * * @since 0.1.0 */ -public interface CardCertificateSignerSpi { +public interface CalypsoCardCertificateSignerSpi { /** * Gets the reference to the issuer's public key. From ce3d3577c2d1f5aaf61da2a6501311933aa983f4 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Thu, 15 Feb 2024 17:24:44 +0100 Subject: [PATCH 03/13] wip --- README.md | 2 +- .../certificate/CalypsoCertificateApiFactory.java | 4 ++-- .../certificate/ca/CalypsoCaCertificateV1.java | 2 +- .../ca/CalypsoCaCertificateV1Builder.java | 14 ++++++++------ .../certificate/card/CalypsoCardCertificateV1.java | 2 +- .../card/CalypsoCardCertificateV1Builder.java | 11 ++++++----- 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index de92af5..3cf4eb3 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ to create Calypso certificates. ## Documentation & Contribution Guide The full documentation, including the **user guide**, **download information** and **contribution guide**, is available -on the Keyple website [keypop.org](https://eclipse-keypop.github.io/keypop-website/). +on the Keypop website [keypop.org](https://eclipse-keypop.github.io/keypop-website/). ## API documentation diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java index f7e03b4..b626c7b 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java @@ -20,7 +20,7 @@ public interface CalypsoCertificateApiFactory { /** - * Returns a builder of Calypso CA certificate version 1. + * Returns a new builder of Calypso CA certificate version 1. * * @return A non-null reference. * @since 0.1.0 @@ -28,7 +28,7 @@ public interface CalypsoCertificateApiFactory { CalypsoCaCertificateV1Builder createCalypsoCaCertificateV1Builder(); /** - * Returns a builder of Calypso card certificate version 1. + * Returns a new builder of Calypso card certificate version 1. * * @return A non-null reference. * @since 0.1.0 diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java index 2a24b2d..5293c53 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java @@ -14,7 +14,7 @@ * * @since 0.1.0 */ -public interface CalypsoCaCertificateV1 { +public interface CalypsoCaCertificateV1 extends CaCertificate { /** * Returns a byte array corresponding to the certificate as it is stored in the card. diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java index b6a6da2..08027d4 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java @@ -11,6 +11,9 @@ import java.security.PrivateKey; import java.security.PublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; + import org.eclipse.keypop.calypso.certificate.ca.spi.CalypsoCaCertificateSignerSpi; /** @@ -24,17 +27,16 @@ public interface CalypsoCaCertificateV1Builder { * * @param caCertificateSigner The external signer for ca certificate generation. * @return The current instance. - * @throws IllegalArgumentException If the provided signer is null, invalid, or not compatible - * with the required signature formats. + * @throws IllegalArgumentException If the provided signer is null. * @throws IllegalStateException If an internal signer has already been configured using {@link - * #useInternalSigner(PrivateKey, byte[])}. + * #useInternalSigner(RSAPrivateKey, byte[])}. * @since 0.1.0 */ CalypsoCaCertificateV1Builder useExternalSigner( CalypsoCaCertificateSignerSpi caCertificateSigner); /** - * Configures the settings to use the internal signer for generating signed CA certificates. + * Configures the builder to use the internal signer for generating signed CA certificates. * *

The internal signer will use the provided 2048 bits RSA private key with a public exponent * of 65537 and the specified public key reference for signing operations. @@ -50,7 +52,7 @@ CalypsoCaCertificateV1Builder useExternalSigner( * @since 0.1.0 */ CalypsoCaCertificateV1Builder useInternalSigner( - PrivateKey issuerPrivateKey, byte[] issuerPublicKeyReference); + RSAPrivateKey issuerPrivateKey, byte[] issuerPublicKeyReference); /** * Sets the public key of the CA, provided as a 64-byte raw array. @@ -66,7 +68,7 @@ CalypsoCaCertificateV1Builder useInternalSigner( * @throws IllegalArgumentException If one of the provided argument is null or invalid. * @since 0.1.0 */ - CalypsoCaCertificateV1Builder setCaPublicKey(PublicKey caPublicKey, byte[] caPublicKeyReference); + CalypsoCaCertificateV1Builder setCaPublicKey(RSAPublicKey caPublicKey, byte[] caPublicKeyReference); /** * Sets the validity period of the certificate's public key. This defines the timeframe when the diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java index 84ac954..f2a879b 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java @@ -14,7 +14,7 @@ * * @since 0.1.0 */ -public interface CalypsoCardCertificateV1 { +public interface CalypsoCardCertificateV1 extends CardCertificate { /** * Returns a byte array corresponding to the certificate as it is stored in the card. diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java index b487961..b8415aa 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java @@ -10,6 +10,8 @@ package org.eclipse.keypop.calypso.certificate.card; import java.security.PrivateKey; +import java.security.interfaces.RSAPrivateKey; + import org.eclipse.keypop.calypso.certificate.card.spi.CalypsoCardCertificateSignerSpi; /** @@ -24,17 +26,16 @@ public interface CalypsoCardCertificateV1Builder { * * @param cardCertificateSigner The external signer for card certificate generation. * @return The current instance. - * @throws IllegalArgumentException If the provided signer is null, invalid, or not compatible - * with the required signature formats. + * @throws IllegalArgumentException If the provided signer is null. * @throws IllegalStateException If an internal signer has already been configured using {@link - * #useInternalSigner(PrivateKey, byte[])}. + * #useInternalSigner(RSAPrivateKey, byte[])}. * @since 0.1.0 */ CalypsoCardCertificateV1Builder useExternalSigner( CalypsoCardCertificateSignerSpi cardCertificateSigner); /** - * Configures the settings to use the internal signer for generating signed card certificates. + * Configures the builder to use the internal signer for generating signed card certificates. * *

The internal signer will use the provided 2048 bits RSA private key with a public exponent * of 65537 and the specified public key reference for signing operations. @@ -50,7 +51,7 @@ CalypsoCardCertificateV1Builder useExternalSigner( * @since 0.1.0 */ CalypsoCardCertificateV1Builder useInternalSigner( - PrivateKey issuerPrivateKey, byte[] issuerPublicKeyReference); + RSAPrivateKey issuerPrivateKey, byte[] issuerPublicKeyReference); /** * Sets the public key of the card, provided as a 64-byte raw array. From 37746250295b6566594b8f9e83e794158ab23d2a Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Fri, 16 Feb 2024 12:14:00 +0100 Subject: [PATCH 04/13] wip --- build.gradle.kts | 1 + .../CertificateSigningException.java | 37 +++++ .../ca/CalypsoCaCertificateV1.java | 5 + .../ca/CalypsoCaCertificateV1Builder.java | 151 ++++++++---------- .../calypso/certificate/ca/package-info.java | 4 +- .../ca/spi/CalypsoCaCertificateSignerSpi.java | 20 +-- .../certificate/ca/spi/package-info.java | 4 +- .../card/CalypsoCardCertificateV1.java | 5 + .../card/CalypsoCardCertificateV1Builder.java | 120 +++++++------- .../certificate/card/package-info.java | 4 +- .../spi/CalypsoCardCertificateSignerSpi.java | 20 +-- .../certificate/card/spi/package-info.java | 4 +- .../calypso/certificate/package-info.java | 4 +- 13 files changed, 194 insertions(+), 185 deletions(-) create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/CertificateSigningException.java diff --git a/build.gradle.kts b/build.gradle.kts index 22a2101..d3ab0b0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,6 +24,7 @@ repositories { mavenCentral() } dependencies { + implementation("org.eclipse.keypop:keypop-calypso-card-java-api:2.1.0-SNAPSHOT") { isChanging = true } testImplementation("junit:junit:4.13.2") testImplementation("org.assertj:assertj-core:3.15.0") } diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateSigningException.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateSigningException.java new file mode 100644 index 0000000..59e3507 --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateSigningException.java @@ -0,0 +1,37 @@ +/* ****************************************************************************** + * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + ****************************************************************************** */ +package org.eclipse.keypop.calypso.certificate; + +/** + * Exception that is thrown when an error occurs during the certificate signing process. + * + * @since 0.1.0 + */ +public class CertificateSigningException extends RuntimeException { + + /** + * @param message Message to identify the exception context. + * @since 0.1.0 + */ + public CertificateSigningException(String message) { + super(message); + } + + /** + * Encapsulates a lower level exception. + * + * @param message Message to identify the exception context. + * @param cause The cause. + * @since 0.1.0 + */ + public CertificateSigningException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java index 5293c53..186b6a9 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java @@ -9,9 +9,14 @@ ****************************************************************************** */ package org.eclipse.keypop.calypso.certificate.ca; +import org.eclipse.keypop.calypso.card.transaction.spi.CaCertificate; + /** * A Calypso CA certificate version 1. * + *

It can be used for certificate generation and card transaction security settings. + * + * @see CaCertificate * @since 0.1.0 */ public interface CalypsoCaCertificateV1 extends CaCertificate { diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java index 08027d4..9d654df 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java @@ -9,94 +9,69 @@ ****************************************************************************** */ package org.eclipse.keypop.calypso.certificate.ca; -import java.security.PrivateKey; -import java.security.PublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; - +import org.eclipse.keypop.calypso.certificate.CertificateSigningException; import org.eclipse.keypop.calypso.certificate.ca.spi.CalypsoCaCertificateSignerSpi; /** - * Builds CA certificates conforming to version 1 of the Calypso CA certificate format. + * Builds a {@link CalypsoCaCertificateV1} conforming to version 1 of the Calypso CA certificate + * format. * * @since 0.1.0 */ public interface CalypsoCaCertificateV1Builder { - /** - * Sets the external signer to be used for generating signed CA certificates. - * - * @param caCertificateSigner The external signer for ca certificate generation. - * @return The current instance. - * @throws IllegalArgumentException If the provided signer is null. - * @throws IllegalStateException If an internal signer has already been configured using {@link - * #useInternalSigner(RSAPrivateKey, byte[])}. - * @since 0.1.0 - */ - CalypsoCaCertificateV1Builder useExternalSigner( - CalypsoCaCertificateSignerSpi caCertificateSigner); /** - * Configures the builder to use the internal signer for generating signed CA certificates. + * Sets the public key of the CA. * - *

The internal signer will use the provided 2048 bits RSA private key with a public exponent - * of 65537 and the specified public key reference for signing operations. + *

This key is expected to be a 2048 bits RSA public key with a public exponent equal to 65537. + * It will be used for the verification of card certificates. * - * @param issuerPrivateKey The RSA private key of the issuer (2048 bits, public exponent 65537). - * @param issuerPublicKeyReference A 29-byte byte array representing a reference to the issuer's - * public key. + *

The associated reference is a 29-byte byte array. + * + * @param caPublicKey The RSA public key of the CA (2048 bits, public exponent 65537). + * @param caPublicKeyReference A 29-byte byte array representing a reference to the CA's public + * key. * @return The current instance. - * @throws IllegalArgumentException If any of the provided arguments are null, invalid, or have - * incompatible formats. - * @throws IllegalStateException If an external signer has already been set using {@link - * #useExternalSigner(CalypsoCaCertificateSignerSpi)}. + * @throws IllegalArgumentException If one of the provided argument is null or invalid. * @since 0.1.0 */ - CalypsoCaCertificateV1Builder useInternalSigner( - RSAPrivateKey issuerPrivateKey, byte[] issuerPublicKeyReference); + CalypsoCaCertificateV1Builder withCaPublicKey( + RSAPublicKey caPublicKey, byte[] caPublicKeyReference); /** - * Sets the public key of the CA, provided as a 64-byte raw array. + * Sets the start date of the validity period of the certificate's public key. * - *

This key is expected to be a 2048 bits RSA public key with a public exponent equal to 65537. - * It will be used for the verification of card certificates. + *

No consistency test is performed on the values supplied, as they will be coded in BCD + * YYYYMMDD format in the certificate. * - *

The associated reference is a 29-byte byte array. - * - * @param caPublicKey The RSA public key of the CA (2048 bits, public exponent 65537). - * @param caPublicKeyReference The CA public key reference. + * @param year The year of the start date (0-9999). + * @param month The month of the start date (1-99). + * @param day The day of the start date (1-99). * @return The current instance. - * @throws IllegalArgumentException If one of the provided argument is null or invalid. + * @throws IllegalArgumentException If any date parameter is out of range. * @since 0.1.0 */ - CalypsoCaCertificateV1Builder setCaPublicKey(RSAPublicKey caPublicKey, byte[] caPublicKeyReference); + CalypsoCaCertificateV1Builder withStartDate(int year, int month, int day); /** - * Sets the validity period of the certificate's public key. This defines the timeframe when the - * certificate can be considered trusted. + * Sets the end date of the validity period of the certificate's public key. * - *

If neither start nor end date is set, the certificate will have open-ended validity. + *

No consistency test is performed on the values supplied, as they will be coded in BCD + * YYYYMMDD format in the certificate. * - * @param startDateYear The year of the start date (e.g., 2024). Valid range: 1900-2100. - * @param startDateMonth The month of the start date (1-12). - * @param startDateDay The day of the start date (1-31). - * @param endDateYear The year of the end date (e.g., 2025). Valid range: 1900-2100. - * @param endDateMonth The month of the end date (1-12). - * @param endDateDay The day of the end date. + * @param year The year of the start date (0-9999). + * @param month The month of the start date (1-99). + * @param day The day of the start date (1-99). * @return The current instance. * @throws IllegalArgumentException If any date parameter is out of range. * @since 0.1.0 */ - CalypsoCaCertificateV1Builder setValidityPeriod( - int startDateYear, - int startDateMonth, - int startDateDay, - int endDateYear, - int endDateMonth, - int endDateDay); + CalypsoCaCertificateV1Builder withEndDate(int year, int month, int day); /** - * Sets the AID (Application Identifier) of the card certificates for which the certificate is - * applicable. + * Restricts certificate validity to cards whose AID begins with the bytes provided. * *

If the AID is not set, the certificate will be applicable to any card certificates. * @@ -105,18 +80,16 @@ CalypsoCaCertificateV1Builder setValidityPeriod( * @throws IllegalArgumentException If the provided argument is null or out of range. * @since 0.1.0 */ - CalypsoCaCertificateV1Builder setAid(byte[] aid); + CalypsoCaCertificateV1Builder withAid(byte[] aid); /** - * Sets the CA rights for this card certificate, controlling which types of certificates the card - * can be used to authenticate. + * Sets the CA rights for this card certificate, controlling which types of certificates can be + * authenticated. * - *

The provided caRights byte defines the following permissions: + *

The provided byte defines the following permissions: * *

    - *
  • Bits b7-b4: Reserved for future use (RFU). Must be set to 0.
    - * Attempting to set non-zero values in these bits will throw an {@link - * IllegalArgumentException}. + *
  • Bits b7-b4: Reserved for future use (RFU). Must be set to 0. *
  • Bits b3-b2: Card key certificates authentication right: *
      *
    • %00: CardCert authentication right not specified. @@ -135,17 +108,16 @@ CalypsoCaCertificateV1Builder setValidityPeriod( * * @param caRights The byte representing the CA rights for this card certificate. * @return The current instance. - * @throws IllegalArgumentException If the provided byte contains invalid values, including - * non-zero values in reserved bits. + * @throws IllegalArgumentException If the provided byte contains RFU values. * @since 0.1.0 */ - CalypsoCaCertificateV1Builder setCaRights(byte caRights); + CalypsoCaCertificateV1Builder withCaRights(byte caRights); /** * Sets the CA scope for this card certificate, defining the context in which the CA key pair can * be used. * - *

      The provided caScope byte specifies the allowed usage context: + *

      The provided byte specifies the allowed usage context: * *

        *
      • %00: Scope restrictions not specified. @@ -154,27 +126,21 @@ CalypsoCaCertificateV1Builder setValidityPeriod( *
      • Other values: Reserved for future use (RFU). *
      * - * Choosing an appropriate scope is crucial for security and proper certificate management. Select - * a scope that aligns with the intended use of the CA key pair to avoid potential misuse. - * * @param caScope The byte representing the CA scope for this card certificate. * @return The current instance. - * @throws IllegalArgumentException If the provided byte contains an invalid value, including - * non-standard or reserved values. + * @throws IllegalArgumentException If the provided byte contains RFU values. * @since 0.1.0 */ - CalypsoCaCertificateV1Builder setCaScope(byte caScope); + CalypsoCaCertificateV1Builder withCaScope(byte caScope); /** * Sets the CA operating mode, controlling how the target Calypso Prime PKI application AID should * be verified during card certificate validation. * - *

      The provided caOperatingMode byte defines the following behavior: + *

      The provided byte defines the following behavior: * *

        - *
      • Bits b7-b1: Reserved for future use (RFU). Must be set to 0.
        - * Attempting to set non-zero values in these bits will throw an {@link - * IllegalArgumentException}. + *
      • Bits b7-b1: Reserved for future use (RFU). Must be set to 0. *
      • Bit b0: Target Calypso Prime PKI application AID matching: *
          *
        • %0: Truncation forbidden: @@ -200,16 +166,37 @@ CalypsoCaCertificateV1Builder setValidityPeriod( * * @param caOperatingMode The byte representing the CA operating mode for this card certificate. * @return The current instance. - * @throws IllegalArgumentException If the provided byte contains invalid values, including - * non-zero values in reserved bits. + * @throws IllegalArgumentException If the provided byte contains RFU values. * @since 0.1.0 */ - CalypsoCaCertificateV1Builder setCaOperatingMode(byte caOperatingMode); + CalypsoCaCertificateV1Builder withCaOperatingMode(byte caOperatingMode); /** - * TODO + * Checks the consistency of the parameters, signs the certificate using the provided private key + * and returns a new instance of {@link CalypsoCaCertificateV1}. * - * @return + *

          The internal signer will use the provided 2048 bits RSA private key with a public exponent + * of 65537 and the specified public key reference for signing operations. + * + * @param issuerPrivateKey The RSA private key of the issuer (2048 bits, public exponent 65537). + * @param issuerPublicKeyReference A 29-byte byte array representing a reference to the issuer's + * public key. + * @return A non-null reference. + * @throws IllegalArgumentException If one of the provided arguments is null. + * @throws CertificateSigningException If an error occurs during the signing process. + * @since 0.1.0 + */ + CalypsoCaCertificateV1 build(RSAPrivateKey issuerPrivateKey, byte[] issuerPublicKeyReference); + + /** + * Checks the consistency of the parameters, signs the certificate using the provided signer and + * returns a new instance of {@link CalypsoCaCertificateV1}. + * + * @param caCertificateSigner The external signer to use for signing the CA certificate. + * @return A non-null reference. + * @throws IllegalArgumentException If the provided signer is null. + * @throws CertificateSigningException If an error occurs during the signing process. + * @since 0.1.0 */ - CalypsoCaCertificateV1 build(); + CalypsoCaCertificateV1 build(CalypsoCaCertificateSignerSpi caCertificateSigner); } diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/package-info.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/package-info.java index 887d03c..e839c6f 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/package-info.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/package-info.java @@ -1,6 +1,6 @@ /** - * Calypso Certification Authority certificates management. + * Interfaces related to Calypso CA certificates. * - * @since 0.2.0 + * @since 0.1.0 */ package org.eclipse.keypop.calypso.certificate.ca; diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/CalypsoCaCertificateSignerSpi.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/CalypsoCaCertificateSignerSpi.java index dd704bc..6f0e4e0 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/CalypsoCaCertificateSignerSpi.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/CalypsoCaCertificateSignerSpi.java @@ -20,7 +20,7 @@ public interface CalypsoCaCertificateSignerSpi { /** - * Gets the reference to the issuer's public key. + * Returns the reference to the issuer's public key. * * @return A 29-byte byte array. * @since 0.1.0 @@ -28,26 +28,14 @@ public interface CalypsoCaCertificateSignerSpi { byte[] getIssuerPublicKeyReference(); /** - * Generates a signed CA certificate based on the provided data. - * - * @param allData The byte array containing all the data to be included in the certificate. This - * typically includes information about the subject, validity period, and other certificate - * attributes. - * @return The signed card certificate, represented as a byte array. - * @since 0.1.0 - */ - byte[] generateSignedCertificate(byte[] allData); - - /** - * Generates a signed CA certificate based on the provided data and recoverable data. This method - * is similar to {@link #generateSignedCertificate(byte[])}, but allows for separate handling of - * non-recoverable and recoverable data during signing. + * Generates a signed CA certificate based on the provided data and recoverable data. * * @param data The byte array containing the non-recoverable data for the certificate. * @param recoverableData The byte array containing the recoverable data for the certificate. This * might be encrypted or protected data that shouldn't be included in the final certificate * but is needed for signature generation. - * @return The signed card certificate, represented as a byte array. + * @return The signed CA certificate, a 384-byte byte array containing the data followed by the + * signature. * @since 0.1.0 */ byte[] generateSignedCertificate(byte[] data, byte[] recoverableData); diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/package-info.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/package-info.java index 08349ec..455d880 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/package-info.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/package-info.java @@ -1,6 +1,6 @@ /** - * Calypso Certification Authority certificates external signing. + * SPIs to be implemented by end user applications related to Calypso CA certificates signing. * - * @since 0.2.0 + * @since 0.1.0 */ package org.eclipse.keypop.calypso.certificate.ca.spi; diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java index f2a879b..10b1f2a 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java @@ -9,9 +9,14 @@ ****************************************************************************** */ package org.eclipse.keypop.calypso.certificate.card; +import org.eclipse.keypop.calypso.card.transaction.spi.CardCertificate; + /** * A Calypso card certificate version 1. * + *

          It can be used for certificate generation and external certificate validation. + * + * @see CardCertificate * @since 0.1.0 */ public interface CalypsoCardCertificateV1 extends CardCertificate { diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java index b8415aa..bb364f1 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java @@ -9,110 +9,86 @@ ****************************************************************************** */ package org.eclipse.keypop.calypso.certificate.card; -import java.security.PrivateKey; import java.security.interfaces.RSAPrivateKey; - +import org.eclipse.keypop.calypso.certificate.CertificateSigningException; import org.eclipse.keypop.calypso.certificate.card.spi.CalypsoCardCertificateSignerSpi; /** - * Builds a card certificate conforming to version 1 of the Calypso card certificate format. + * Builds a {@link CalypsoCardCertificateV1} conforming to version 1 of the Calypso card certificate + * format. * * @since 0.1.0 */ public interface CalypsoCardCertificateV1Builder { /** - * Sets the external signer to be used for generating signed card certificates. - * - * @param cardCertificateSigner The external signer for card certificate generation. - * @return The current instance. - * @throws IllegalArgumentException If the provided signer is null. - * @throws IllegalStateException If an internal signer has already been configured using {@link - * #useInternalSigner(RSAPrivateKey, byte[])}. - * @since 0.1.0 - */ - CalypsoCardCertificateV1Builder useExternalSigner( - CalypsoCardCertificateSignerSpi cardCertificateSigner); - - /** - * Configures the builder to use the internal signer for generating signed card certificates. + * Sets the public key of the card, provided as a 64-byte raw array. * - *

          The internal signer will use the provided 2048 bits RSA private key with a public exponent - * of 65537 and the specified public key reference for signing operations. + *

          This key is expected to be on the secp256r1 elliptic curve. It will be used + * for the verification of card signatures. * - * @param issuerPrivateKey The RSA private key of the issuer (2048 bits, public exponent 65537). - * @param issuerPublicKeyReference A 29-byte byte array representing a reference to the issuer's - * public key. + * @param cardPublicKey The 64-byte raw array representing the public key on the + * secp256r1 curve. * @return The current instance. - * @throws IllegalArgumentException If any of the provided arguments are null, invalid, or have - * incompatible formats. - * @throws IllegalStateException If an external signer has already been set using {@link - * #useExternalSigner(CalypsoCardCertificateSignerSpi)}. + * @throws IllegalArgumentException If the provided key is null or has an invalid length (not 64). * @since 0.1.0 */ - CalypsoCardCertificateV1Builder useInternalSigner( - RSAPrivateKey issuerPrivateKey, byte[] issuerPublicKeyReference); + CalypsoCardCertificateV1Builder withCardPublicKey(byte[] cardPublicKey); /** - * Sets the public key of the card, provided as a 64-byte raw array. + * Sets the start date of the validity period of the certificate's public key. * - *

          This key is expected to be on the secp256r1 elliptic curve. It will be used - * for the verification of card signatures. + *

          No consistency test is performed on the values supplied, as they will be coded in BCD + * YYYYMMDD format in the certificate. * - * @param cardPublicKey The 64-byte raw array representing the public key on the - * secp256r1 curve. + * @param year The year of the start date (0-9999). + * @param month The month of the start date (1-99). + * @param day The day of the start date (1-99). * @return The current instance. - * @throws IllegalArgumentException If the provided key is null or has an invalid length (not 64). + * @throws IllegalArgumentException If any date parameter is out of range. * @since 0.1.0 */ - CalypsoCardCertificateV1Builder setCardPublicKey(byte[] cardPublicKey); + CalypsoCardCertificateV1Builder withStartDate(int year, int month, int day); /** - * Sets the validity period of the certificate's public key. This defines the timeframe when the - * certificate can be considered trusted. + * Sets the end date of the validity period of the certificate's public key. * - *

          If neither start nor end date is set, the certificate will have open-ended validity. + *

          No consistency test is performed on the values supplied, as they will be coded in BCD + * YYYYMMDD format in the certificate. * - * @param startDateYear The year of the start date (e.g., 2024). Valid range: 1900-2100. - * @param startDateMonth The month of the start date (1-12). - * @param startDateDay The day of the start date (1-31). - * @param endDateYear The year of the end date (e.g., 2025). Valid range: 1900-2100. - * @param endDateMonth The month of the end date (1-12). - * @param endDateDay The day of the end date. + * @param year The year of the start date (0-9999). + * @param month The month of the start date (1-99). + * @param day The day of the start date (1-99). * @return The current instance. * @throws IllegalArgumentException If any date parameter is out of range. * @since 0.1.0 */ - CalypsoCardCertificateV1Builder setValidityPeriod( - int startDateYear, - int startDateMonth, - int startDateDay, - int endDateYear, - int endDateMonth, - int endDateDay); + CalypsoCardCertificateV1Builder withEndDate(int year, int month, int day); /** - * Sets the AID (Application Identifier) associated with the certificate. + * Restricts certificate validity to cards whose AID begins with the bytes provided. + * + *

          If the AID is not set, the certificate will be applicable to any card certificates. * * @param aid The AID value as a 5 to 16 bytes byte array. * @return The current instance. * @throws IllegalArgumentException If the provided argument is null or out of range. * @since 0.1.0 */ - CalypsoCardCertificateV1Builder setAid(byte[] aid); + CalypsoCardCertificateV1Builder withAid(byte[] aid); /** * Sets the serial number of the card for which the certificate is being generated. * - * @param serialNumber The serial number of the card as a 8-byte byte array. + * @param serialNumber The serial number of the card as an 8-byte byte array. * @return The current instance. * @throws IllegalArgumentException If the provided argument is null or out of range. * @since 0.1.0 */ - CalypsoCardCertificateV1Builder setCardSerialNumber(byte[] serialNumber); + CalypsoCardCertificateV1Builder withCardSerialNumber(byte[] serialNumber); /** - * Sets the startup info for the card certificate. + * Sets the startup info of the card for which the certificate is being generated. * * @param startupInfo The 7-byte byte array representing the startup info for the card * certificate. @@ -120,7 +96,7 @@ CalypsoCardCertificateV1Builder setValidityPeriod( * @throws IllegalArgumentException If the provided argument is null or out of range. * @since 0.1.0 */ - CalypsoCardCertificateV1Builder setCardStartupInfo(byte[] startupInfo); + CalypsoCardCertificateV1Builder withCardStartupInfo(byte[] startupInfo); /** * Sets the index used to differentiate two card certificates generated with the same issuer @@ -130,12 +106,34 @@ CalypsoCardCertificateV1Builder setValidityPeriod( * @return The current instance. * @since 0.1.0 */ - CalypsoCardCertificateV1Builder setIndex(int index); + CalypsoCardCertificateV1Builder withIndex(int index); /** - * TODO + * Checks the consistency of the parameters, signs the certificate using the provided private key + * and returns a new instance of {@link CalypsoCardCertificateV1}. * - * @return + *

          The internal signer will use the provided 2048 bits RSA private key with a public exponent + * of 65537 and the specified public key reference for signing operations. + * + * @param issuerPrivateKey The RSA private key of the issuer (2048 bits, public exponent 65537). + * @param issuerPublicKeyReference A 29-byte byte array representing a reference to the issuer's + * public key. + * @return A non-null reference. + * @throws IllegalArgumentException If one of the provided arguments is null. + * @throws CertificateSigningException If an error occurs during the signing process. + * @since 0.1.0 + */ + CalypsoCardCertificateV1 build(RSAPrivateKey issuerPrivateKey, byte[] issuerPublicKeyReference); + + /** + * Checks the consistency of the parameters, signs the certificate using the provided signer and + * returns a new instance of {@link CalypsoCardCertificateV1}. + * + * @param cardCertificateSigner The external signer for card certificate generation. + * @return A non-null reference. + * @throws IllegalArgumentException If the provided signer is null. + * @throws CertificateSigningException If an error occurs during the signing process. + * @since 0.1.0 */ - CalypsoCardCertificateV1 build(); + CalypsoCardCertificateV1 build(CalypsoCardCertificateSignerSpi cardCertificateSigner); } diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/package-info.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/package-info.java index 5f785db..3239034 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/package-info.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/package-info.java @@ -1,6 +1,6 @@ /** - * Calypso card certificates management. + * Interfaces related to Calypso card certificates. * - * @since 0.2.0 + * @since 0.1.0 */ package org.eclipse.keypop.calypso.certificate.card; diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/CalypsoCardCertificateSignerSpi.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/CalypsoCardCertificateSignerSpi.java index 5aa6797..15ed3c5 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/CalypsoCardCertificateSignerSpi.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/CalypsoCardCertificateSignerSpi.java @@ -20,7 +20,7 @@ public interface CalypsoCardCertificateSignerSpi { /** - * Gets the reference to the issuer's public key. + * Returns the reference to the issuer's public key. * * @return A 29-byte byte array. * @since 0.1.0 @@ -28,26 +28,14 @@ public interface CalypsoCardCertificateSignerSpi { byte[] getIssuerPublicKeyReference(); /** - * Generates a signed card certificate based on the provided data. - * - * @param allData The byte array containing all the data to be included in the certificate. This - * typically includes information about the subject, validity period, and other certificate - * attributes. - * @return The signed card certificate, represented as a byte array. - * @since 0.1.0 - */ - byte[] generateSignedCertificate(byte[] allData); - - /** - * Generates a signed card certificate based on the provided data and recoverable data. This - * method is similar to {@link #generateSignedCertificate(byte[])}, but allows for separate - * handling of non-recoverable and recoverable data during signing. + * Generates a signed card certificate based on the provided data and recoverable data. * * @param data The byte array containing the non-recoverable data for the certificate. * @param recoverableData The byte array containing the recoverable data for the certificate. This * might be encrypted or protected data that shouldn't be included in the final certificate * but is needed for signature generation. - * @return The signed card certificate, represented as a byte array. + * @return The signed card certificate, a 316-byte byte array containing the data followed by the + * signature. * @since 0.1.0 */ byte[] generateSignedCertificate(byte[] data, byte[] recoverableData); diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/package-info.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/package-info.java index cd85713..b3efe25 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/package-info.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/package-info.java @@ -1,6 +1,6 @@ /** - * Calypso card certificate external signing. + * SPIs to be implemented by end user applications related to Calypso card certificates signing. * - * @since 0.2.0 + * @since 0.1.0 */ package org.eclipse.keypop.calypso.certificate.card.spi; diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/package-info.java b/src/main/java/org/eclipse/keypop/calypso/certificate/package-info.java index ce0d140..4abf674 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/package-info.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/package-info.java @@ -1,6 +1,6 @@ /** - * Calypso Certification Authority and card certificates management. + * Interfaces related to Calypso certificates. * - * @since 0.2.0 + * @since 0.1.0 */ package org.eclipse.keypop.calypso.certificate; From f891d1df2bc234e6cfcdeb39b3efb6a318bcce4b Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Fri, 16 Feb 2024 17:39:02 +0100 Subject: [PATCH 05/13] wip --- .../ca/CalypsoCaCertificateV1Builder.java | 76 ++++++++----------- .../card/CalypsoCardCertificateV1Builder.java | 25 ++++-- 2 files changed, 51 insertions(+), 50 deletions(-) diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java index 9d654df..3ccf050 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java @@ -46,6 +46,9 @@ CalypsoCaCertificateV1Builder withCaPublicKey( *

          No consistency test is performed on the values supplied, as they will be coded in BCD * YYYYMMDD format in the certificate. * + *

          The start date is optional. If it is not defined, the certificate is not subject to a start + * date constraint. + * * @param year The year of the start date (0-9999). * @param month The month of the start date (1-99). * @param day The day of the start date (1-99). @@ -61,6 +64,9 @@ CalypsoCaCertificateV1Builder withCaPublicKey( *

          No consistency test is performed on the values supplied, as they will be coded in BCD * YYYYMMDD format in the certificate. * + *

          The end date is optional. If it is not defined, the certificate is not subject to an end + * date constraint. + * * @param year The year of the start date (0-9999). * @param month The month of the start date (1-99). * @param day The day of the start date (1-99). @@ -71,16 +77,32 @@ CalypsoCaCertificateV1Builder withCaPublicKey( CalypsoCaCertificateV1Builder withEndDate(int year, int month, int day); /** - * Restricts certificate validity to cards whose AID begins with the bytes provided. + * Restricts certificate validity to cards whose Application Identifier (AID) begins with the + * bytes provided. + * + *

          This method allows you to specify an AID value to limit the applicability of the + * certificate. The certificate will only be valid for cards whose AID starts with the provided + * bytes. + * + *

          The AID is optional. When not set, no restriction related to the card AID will be applied. + * + *

          Important: + * + *

          The aid field cannot contain only zero bytes. * - *

          If the AID is not set, the certificate will be applicable to any card certificates. + *

          The isTruncated field indicates whether the provided AID is truncated. If set to + * true, the certificate will be valid for cards whose AID starts with the provided bytes, + * even if the card's full AID is longer. If set to false, the certificate will only be + * valid for cards whose full AID exactly matches the provided bytes. * - * @param aid The AID value as a 5 to 16 bytes byte array. + * @param aid The AID value as a 5 to 16 bytes byte array. Must not contain only zero bytes. + * @param isTruncated true if the provided AID is truncated, false otherwise. * @return The current instance. - * @throws IllegalArgumentException If the provided argument is null or out of range. + * @throws IllegalArgumentException If the provided AID is null, out of range, or contains only + * zero bytes. * @since 0.1.0 */ - CalypsoCaCertificateV1Builder withAid(byte[] aid); + CalypsoCaCertificateV1Builder withAid(byte[] aid, boolean isTruncated); /** * Sets the CA rights for this card certificate, controlling which types of certificates can be @@ -106,6 +128,8 @@ CalypsoCaCertificateV1Builder withCaPublicKey( *

        *
      * + * The CA rights byte is optional. If not set, the default value is 0. + * * @param caRights The byte representing the CA rights for this card certificate. * @return The current instance. * @throws IllegalArgumentException If the provided byte contains RFU values. @@ -126,6 +150,8 @@ CalypsoCaCertificateV1Builder withCaPublicKey( *
    • Other values: Reserved for future use (RFU). *
    * + * The CA scope byte is optional. If not set, the default value is 0. + * * @param caScope The byte representing the CA scope for this card certificate. * @return The current instance. * @throws IllegalArgumentException If the provided byte contains RFU values. @@ -133,44 +159,6 @@ CalypsoCaCertificateV1Builder withCaPublicKey( */ CalypsoCaCertificateV1Builder withCaScope(byte caScope); - /** - * Sets the CA operating mode, controlling how the target Calypso Prime PKI application AID should - * be verified during card certificate validation. - * - *

    The provided byte defines the following behavior: - * - *

      - *
    • Bits b7-b1: Reserved for future use (RFU). Must be set to 0. - *
    • Bit b0: Target Calypso Prime PKI application AID matching: - *
        - *
      • %0: Truncation forbidden: - *
          - *
        • The size of the card AID in the card certificate must be equal to the size of - * the target AID specified in this certificate. - *
        • The corresponding number of first (leftmost) bytes of the card AID value in - * the card certificate and the target AID value in this certificate must be - * equal. - *
        - *
      • %1: Truncation allowed: - *
          - *
        • The size of the card AID in the card certificate can be equal to or greater - * than the size of the target AID specified in this certificate. - *
        • The specified size first (leftmost) bytes of the card AID value in the card - * certificate and the target AID value in this certificate must be equal. - *
        - *
      - *
    - * - * Choosing an appropriate operating mode is crucial for secure and verified certificate issuance. - * Refer to the Calypso Prime PKI specifications for further details and guidelines. - * - * @param caOperatingMode The byte representing the CA operating mode for this card certificate. - * @return The current instance. - * @throws IllegalArgumentException If the provided byte contains RFU values. - * @since 0.1.0 - */ - CalypsoCaCertificateV1Builder withCaOperatingMode(byte caOperatingMode); - /** * Checks the consistency of the parameters, signs the certificate using the provided private key * and returns a new instance of {@link CalypsoCaCertificateV1}. @@ -183,6 +171,7 @@ CalypsoCaCertificateV1Builder withCaPublicKey( * public key. * @return A non-null reference. * @throws IllegalArgumentException If one of the provided arguments is null. + * @throws IllegalStateException If one of the required parameters is wrong or missing. * @throws CertificateSigningException If an error occurs during the signing process. * @since 0.1.0 */ @@ -195,6 +184,7 @@ CalypsoCaCertificateV1Builder withCaPublicKey( * @param caCertificateSigner The external signer to use for signing the CA certificate. * @return A non-null reference. * @throws IllegalArgumentException If the provided signer is null. + * @throws IllegalStateException If one of the required parameters is wrong or missing. * @throws CertificateSigningException If an error occurs during the signing process. * @since 0.1.0 */ diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java index bb364f1..b35799e 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java @@ -22,15 +22,15 @@ public interface CalypsoCardCertificateV1Builder { /** - * Sets the public key of the card, provided as a 64-byte raw array. + * Sets the public key of the card, provided as a 64-byte array. * *

    This key is expected to be on the secp256r1 elliptic curve. It will be used * for the verification of card signatures. * - * @param cardPublicKey The 64-byte raw array representing the public key on the + * @param cardPublicKey The 64-byte array representing the public key on the * secp256r1 curve. * @return The current instance. - * @throws IllegalArgumentException If the provided key is null or has an invalid length (not 64). + * @throws IllegalArgumentException If the provided key is null or out of range. * @since 0.1.0 */ CalypsoCardCertificateV1Builder withCardPublicKey(byte[] cardPublicKey); @@ -41,6 +41,9 @@ public interface CalypsoCardCertificateV1Builder { *

    No consistency test is performed on the values supplied, as they will be coded in BCD * YYYYMMDD format in the certificate. * + *

    The start date is optional. If it is not defined, the certificate is not subject to a start + * date constraint. + * * @param year The year of the start date (0-9999). * @param month The month of the start date (1-99). * @param day The day of the start date (1-99). @@ -56,6 +59,9 @@ public interface CalypsoCardCertificateV1Builder { *

    No consistency test is performed on the values supplied, as they will be coded in BCD * YYYYMMDD format in the certificate. * + *

    The end date is optional. If it is not defined, the certificate is not subject to an end + * date constraint. + * * @param year The year of the start date (0-9999). * @param month The month of the start date (1-99). * @param day The day of the start date (1-99). @@ -66,13 +72,14 @@ public interface CalypsoCardCertificateV1Builder { CalypsoCardCertificateV1Builder withEndDate(int year, int month, int day); /** - * Restricts certificate validity to cards whose AID begins with the bytes provided. + * Sets the AID of the autonomous PKI application of the target card. * - *

    If the AID is not set, the certificate will be applicable to any card certificates. + *

    The aid field cannot contain only zero bytes. * - * @param aid The AID value as a 5 to 16 bytes byte array. + * @param aid The AID value as a 5 to 16 bytes byte array. Must not contain only zero bytes. * @return The current instance. - * @throws IllegalArgumentException If the provided argument is null or out of range. + * @throws IllegalArgumentException If the provided AID is null, out of range, or contains only + * zero bytes. * @since 0.1.0 */ CalypsoCardCertificateV1Builder withAid(byte[] aid); @@ -102,6 +109,8 @@ public interface CalypsoCardCertificateV1Builder { * Sets the index used to differentiate two card certificates generated with the same issuer * public key reference for the same card. * + *

    The index is optional. By default, it is set to 0. + * * @param index The index of the card certificate. * @return The current instance. * @since 0.1.0 @@ -120,6 +129,7 @@ public interface CalypsoCardCertificateV1Builder { * public key. * @return A non-null reference. * @throws IllegalArgumentException If one of the provided arguments is null. + * @throws IllegalStateException If one of the required parameters is wrong or missing. * @throws CertificateSigningException If an error occurs during the signing process. * @since 0.1.0 */ @@ -132,6 +142,7 @@ public interface CalypsoCardCertificateV1Builder { * @param cardCertificateSigner The external signer for card certificate generation. * @return A non-null reference. * @throws IllegalArgumentException If the provided signer is null. + * @throws IllegalStateException If one of the required parameters is wrong or missing. * @throws CertificateSigningException If an error occurs during the signing process. * @since 0.1.0 */ From 863423ea19733109acbd8590c00aa25c51fbb921 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Tue, 20 Feb 2024 17:53:21 +0100 Subject: [PATCH 06/13] wip --- .../CalypsoCertificateApiFactory.java | 84 ++++++++++- .../certificate/CalypsoCertificateStore.java | 130 ++++++++++++++++++ .../CertificateConsistencyException.java | 37 +++++ .../certificate/ca/CalypsoCaCertificate.java | 28 ++++ .../ca/CalypsoCaCertificateV1.java | 11 +- .../ca/CalypsoCaCertificateV1Builder.java | 25 +--- .../card/CalypsoCardCertificate.java | 28 ++++ .../card/CalypsoCardCertificateV1.java | 11 +- .../card/CalypsoCardCertificateV1Builder.java | 30 +--- .../CalypsoCertificateSignerSpi.java} | 19 +-- .../{card => }/spi/package-info.java | 4 +- 11 files changed, 321 insertions(+), 86 deletions(-) create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/CertificateConsistencyException.java create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificate.java create mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificate.java rename src/main/java/org/eclipse/keypop/calypso/certificate/{card/spi/CalypsoCardCertificateSignerSpi.java => spi/CalypsoCertificateSignerSpi.java} (69%) rename src/main/java/org/eclipse/keypop/calypso/certificate/{card => }/spi/package-info.java (50%) diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java index b626c7b..1a68741 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java @@ -11,6 +11,7 @@ import org.eclipse.keypop.calypso.certificate.ca.CalypsoCaCertificateV1Builder; import org.eclipse.keypop.calypso.certificate.card.CalypsoCardCertificateV1Builder; +import org.eclipse.keypop.calypso.certificate.spi.CalypsoCertificateSignerSpi; /** * Factory of CA and card certificate builders. @@ -20,18 +21,93 @@ public interface CalypsoCertificateApiFactory { /** - * Returns a new builder of Calypso CA certificate version 1. + * Returns the store where Calypso Certificate Authority (CA) certificates and optionally the + * private keys are injected and made available for certificate generation processes. * + *

    The store contains the necessary certificates and private keys required for generating + * Calypso CA and card certificates. + * + *

    Use the methods provided by this store to inject the appropriate certificates and private + * keys before creating the certificate builders. + * + * @return A non-null reference. + * @since 0.1.0 + */ + CalypsoCertificateStore getCalypsoCertificateStore(); + + /** + * Creates a new builder for Calypso CA certificates (version 1) using the internal signer. + * + *

    The builder is used to configure and build signed Calypso CA certificates. + * + *

    This method must be called after the issuer certificate and its associated private key have + * been injected into the store using the specified issuer public key reference. + * + * @param issuerPublicKeyReference The reference to issuer's public key in the store. + * @throws IllegalStateException If the provided reference is unknown or the issuer's private key + * is missing. + * @throws CertificateConsistencyException If the provided reference doesn't designate a valid + * certification for the operation. + * @return A non-null reference. + * @since 0.1.0 + */ + CalypsoCaCertificateV1Builder createCalypsoCaCertificateV1Builder( + byte[] issuerPublicKeyReference); + + /** + * Creates a new builder for Calypso CA certificates (version 1) using an external signer. + * + *

    The builder is used to configure and build signed Calypso CA certificates. + * + *

    This method must be called after the issuer certificate of the signer using the specified + * issuer public key reference. + * + * @param issuerPublicKeyReference The reference to issuer's public key in the store. + * @param caCertificateSigner The external signer to use for signing the CA certificate. + * @throws IllegalStateException If the provided reference is unknown. + * @throws CertificateConsistencyException If the provided reference doesn't designate a valid + * certification for the operation. + * @return A non-null reference. + * @since 0.1.0 + */ + CalypsoCaCertificateV1Builder createCalypsoCaCertificateV1Builder( + byte[] issuerPublicKeyReference, CalypsoCertificateSignerSpi caCertificateSigner); + + /** + * Creates a new builder for Calypso card certificates (version 1) using the internal signer. + * + *

    The builder is used to configure and build signed Calypso card certificates. + * + *

    This method must be called after the issuer certificate and its associated private key have + * been injected into the store using the specified issuer public key reference. + * + * @param issuerPublicKeyReference The reference to issuer's public key in the store. + * @throws IllegalStateException If the provided reference is unknown or the issuer's private key + * is missing. + * @throws CertificateConsistencyException If the provided reference doesn't designate a valid + * certification for the operation. * @return A non-null reference. * @since 0.1.0 */ - CalypsoCaCertificateV1Builder createCalypsoCaCertificateV1Builder(); + CalypsoCardCertificateV1Builder createCalypsoCardCertificateV1Builder( + byte[] issuerPublicKeyReference); /** - * Returns a new builder of Calypso card certificate version 1. + * Creates a new builder for Calypso card certificates (version 1) using an external signer. + * + *

    The builder is used to configure and build signed Calypso card certificates. + * + *

    This method must be called after the issuer certificate of the signer using the specified + * issuer public key reference. * + * @param issuerPublicKeyReference The reference to issuer's public key in the store. + * @param cardCertificateSigner The external signer to use for signing the card certificate. + * @throws IllegalStateException If the provided reference is unknown. + * @throws CertificateConsistencyException If the provided reference doesn't designate a valid + * certification for the operation. * @return A non-null reference. * @since 0.1.0 */ - CalypsoCardCertificateV1Builder createCalypsoCardCertificateV1Builder(); + CalypsoCardCertificateV1Builder createCalypsoCardCertificateV1Builder( + byte[] issuerPublicKeyReference, CalypsoCertificateSignerSpi cardCertificateSigner); } diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java new file mode 100644 index 0000000..594ddce --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java @@ -0,0 +1,130 @@ +/* ****************************************************************************** + * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + ****************************************************************************** */ +package org.eclipse.keypop.calypso.certificate; + +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import org.eclipse.keypop.calypso.certificate.ca.CalypsoCaCertificate; + +/** + * Provides a store for managing Calypso Certificate Authority (CA) certificates and keys. + * + *

    This interface offers methods for adding the following to the store: + * + *

      + *
    • PCA public keys and key pairs + *
    • Calypso CA certificates + *
    • Calypso CA certificates with their associated private keys + *
    + * + *

    The stored certificates and keys are used for verifying and creating Calypso CA certificates + * within the certificate infrastructure. + * + *

    Certificates and keys must be added to the store in a specific trust order to ensure a proper + * chain of trust within the certificate infrastructure. This order reflects the trust relationships + * between different authorities, allowing certificates to verify their validity up to a trusted + * root. + * + * @since 0.1.0 + */ +public interface CalypsoCertificateStore { + + /** + * Adds a Primary Certification Authority (PCA) public key and its reference. + * + *

    This method expects a 2048-bit RSA public key with an exponent of 65537. + * + *

    The provided public key reference will be used for identifying the key when creating + * certificates within the certificate infrastructure. + * + *

    This method is typically used for scenarios where the need is to verify Calypso CA + * certificates. By providing both the public and private keys, the store has the necessary + * information for these operations. + * + *

    The key reference is to be used when creating certificates according to the desired + * infrastructure. + * + *

    This method is suitable for providing means to verify CA certificates. + * + * @param publicKeyReference The reference to the public key. + * @param publicKey The public key. + * @return The current instance. + * @throws IllegalArgumentException If one of the argument is null or if the key is not a 2048-bit + * RSA key. + * @since 0.1.0 + */ + CalypsoCertificateStore addPcaPublicKey(byte[] publicKeyReference, RSAPublicKey publicKey); + + /** + * Adds a Primary Certification Authority (PCA) public key, its reference, and the associated + * private key. + * + *

    This method expects a 2048-bit RSA key with an exponent of 65537. Both the public part of + * the key and its associated private part must be provided. + * + *

    The provided public key reference will be used for identifying the key when creating + * certificates within the certificate infrastructure. + * + *

    This method is typically used for scenarios where the need is to verify and/or create + * Calypso CA certificates. By providing both the public and private keys, the store has the + * necessary information for these operations. + * + * @param publicKeyReference The reference to the public key. + * @param publicKey The public key. + * @param privateKey The associated private key corresponding to the provided public key. + * @return The current instance. + * @throws IllegalArgumentException If one of the argument is null or if the provided key pair is + * not a valid 2048-bit RSA key pair. + * @since 0.1.0 + */ + CalypsoCertificateStore addPcaKeyPair( + byte[] publicKeyReference, RSAPublicKey publicKey, RSAPrivateKey privateKey); + + /** + * Adds a Calypso Certificate Authority (CA) certificate to the store. + * + *

    This method adds the provided certificate to the store. The certificate must be valid + * (signed by an already referenced authority) and issued by a trusted authority previously + * referenced in the store. + * + *

    This method is typically used to add an intermediate CA certificates necessary for + * validating other certificates within the infrastructure. + * + * @param caCertificate The Calypso CA certificate to add. + * @return The current instance. + * @throws IllegalArgumentException If the certificate is null. + * @throws CertificateConsistencyException If the certificate is not trusted. + * @since 0.1.0 + */ + CalypsoCertificateStore addCalypsoCaCertificate(CalypsoCaCertificate caCertificate); + + /** + * Adds a Calypso Certificate Authority (CA) certificate and its associated private key to the + * store. + * + *

    This method adds the provided certificate and its associated private key to the store. Both + * the certificate must be valid (signed by an already referenced authority) and the private key + * must correspond to the certificate. + * + *

    This method is typically used for scenarios where you need to issue Calypso CA or card + * certificates. By providing both the certificate and its private key, the store has the + * necessary information to sign new certificates. + * + * @param caCertificate The Calypso CA certificate to add. + * @param caPrivateKey The private key associated with the provided certificate. + * @return The current instance. + * @throws IllegalArgumentException If either the certificate or the private key is null or + * invalid, or if they do not correspond to each other. + * @throws CertificateConsistencyException If the certificate is not trusted. + * @since 0.1.0 + */ + CalypsoCertificateStore addCalypsoCaCertificate( + CalypsoCaCertificate caCertificate, RSAPrivateKey caPrivateKey); +} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateConsistencyException.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateConsistencyException.java new file mode 100644 index 0000000..a0eeab8 --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateConsistencyException.java @@ -0,0 +1,37 @@ +/* ****************************************************************************** + * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + ****************************************************************************** */ +package org.eclipse.keypop.calypso.certificate; + +/** + * Exception that is thrown when an error occurs during the certificate validation process. + * + * @since 0.1.0 + */ +public class CertificateConsistencyException extends RuntimeException { + + /** + * @param message Message to identify the exception context. + * @since 0.1.0 + */ + public CertificateConsistencyException(String message) { + super(message); + } + + /** + * Encapsulates a lower level exception. + * + * @param message Message to identify the exception context. + * @param cause The cause. + * @since 0.1.0 + */ + public CertificateConsistencyException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificate.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificate.java new file mode 100644 index 0000000..55f9275 --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificate.java @@ -0,0 +1,28 @@ +/* ****************************************************************************** + * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + ****************************************************************************** */ +package org.eclipse.keypop.calypso.certificate.ca; + +import org.eclipse.keypop.calypso.card.transaction.spi.CaCertificate; + +/** + * CA Certificate compliant with the 384-byte format supported by the Calypso cards. + * + * @since 0.1.0 + */ +public interface CalypsoCaCertificate extends CaCertificate { + + /** + * Returns a byte array corresponding to the certificate as it is stored in the card. + * + * @return A 384-byte byte array. + * @since 0.1.0 + */ + byte[] getRawData(); +} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java index 186b6a9..a1bd824 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java @@ -19,13 +19,4 @@ * @see CaCertificate * @since 0.1.0 */ -public interface CalypsoCaCertificateV1 extends CaCertificate { - - /** - * Returns a byte array corresponding to the certificate as it is stored in the card. - * - * @return A 384-byte byte array. - * @since 0.1.0 - */ - byte[] getRawData(); -} +public interface CalypsoCaCertificateV1 extends CalypsoCaCertificate {} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java index 3ccf050..10e8a20 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java @@ -9,10 +9,9 @@ ****************************************************************************** */ package org.eclipse.keypop.calypso.certificate.ca; -import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; +import org.eclipse.keypop.calypso.certificate.CertificateConsistencyException; import org.eclipse.keypop.calypso.certificate.CertificateSigningException; -import org.eclipse.keypop.calypso.certificate.ca.spi.CalypsoCaCertificateSignerSpi; /** * Builds a {@link CalypsoCaCertificateV1} conforming to version 1 of the Calypso CA certificate @@ -163,30 +162,12 @@ CalypsoCaCertificateV1Builder withCaPublicKey( * Checks the consistency of the parameters, signs the certificate using the provided private key * and returns a new instance of {@link CalypsoCaCertificateV1}. * - *

    The internal signer will use the provided 2048 bits RSA private key with a public exponent - * of 65537 and the specified public key reference for signing operations. - * - * @param issuerPrivateKey The RSA private key of the issuer (2048 bits, public exponent 65537). - * @param issuerPublicKeyReference A 29-byte byte array representing a reference to the issuer's - * public key. * @return A non-null reference. * @throws IllegalArgumentException If one of the provided arguments is null. * @throws IllegalStateException If one of the required parameters is wrong or missing. + * @throws CertificateConsistencyException If the provided parameters are inconsistent. * @throws CertificateSigningException If an error occurs during the signing process. * @since 0.1.0 */ - CalypsoCaCertificateV1 build(RSAPrivateKey issuerPrivateKey, byte[] issuerPublicKeyReference); - - /** - * Checks the consistency of the parameters, signs the certificate using the provided signer and - * returns a new instance of {@link CalypsoCaCertificateV1}. - * - * @param caCertificateSigner The external signer to use for signing the CA certificate. - * @return A non-null reference. - * @throws IllegalArgumentException If the provided signer is null. - * @throws IllegalStateException If one of the required parameters is wrong or missing. - * @throws CertificateSigningException If an error occurs during the signing process. - * @since 0.1.0 - */ - CalypsoCaCertificateV1 build(CalypsoCaCertificateSignerSpi caCertificateSigner); + CalypsoCaCertificateV1 build(); } diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificate.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificate.java new file mode 100644 index 0000000..519f0e1 --- /dev/null +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificate.java @@ -0,0 +1,28 @@ +/* ****************************************************************************** + * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + ****************************************************************************** */ +package org.eclipse.keypop.calypso.certificate.card; + +import org.eclipse.keypop.calypso.card.transaction.spi.CaCertificate; + +/** + * Card Certificate compliant with the 316-byte format supported by the Calypso cards. + * + * @since 0.1.0 + */ +public interface CalypsoCardCertificate extends CaCertificate { + + /** + * Returns a byte array corresponding to the certificate as it is stored in the card. + * + * @return A 316-byte byte array. + * @since 0.1.0 + */ + byte[] getRawData(); +} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java index 10b1f2a..9a5f2e9 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java @@ -19,13 +19,4 @@ * @see CardCertificate * @since 0.1.0 */ -public interface CalypsoCardCertificateV1 extends CardCertificate { - - /** - * Returns a byte array corresponding to the certificate as it is stored in the card. - * - * @return A 316-byte byte array. - * @since 0.1.0 - */ - byte[] getRawData(); -} +public interface CalypsoCardCertificateV1 extends CalypsoCardCertificate {} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java index b35799e..c6c44b1 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java @@ -9,9 +9,8 @@ ****************************************************************************** */ package org.eclipse.keypop.calypso.certificate.card; -import java.security.interfaces.RSAPrivateKey; +import org.eclipse.keypop.calypso.certificate.CertificateConsistencyException; import org.eclipse.keypop.calypso.certificate.CertificateSigningException; -import org.eclipse.keypop.calypso.certificate.card.spi.CalypsoCardCertificateSignerSpi; /** * Builds a {@link CalypsoCardCertificateV1} conforming to version 1 of the Calypso card certificate @@ -118,33 +117,16 @@ public interface CalypsoCardCertificateV1Builder { CalypsoCardCertificateV1Builder withIndex(int index); /** - * Checks the consistency of the parameters, signs the certificate using the provided private key - * and returns a new instance of {@link CalypsoCardCertificateV1}. + * Checks the consistency of the parameters, signs the certificate either using the provided + * private key or the provided signer and returns a new instance of {@link + * CalypsoCardCertificateV1}. * - *

    The internal signer will use the provided 2048 bits RSA private key with a public exponent - * of 65537 and the specified public key reference for signing operations. - * - * @param issuerPrivateKey The RSA private key of the issuer (2048 bits, public exponent 65537). - * @param issuerPublicKeyReference A 29-byte byte array representing a reference to the issuer's - * public key. * @return A non-null reference. * @throws IllegalArgumentException If one of the provided arguments is null. * @throws IllegalStateException If one of the required parameters is wrong or missing. + * @throws CertificateConsistencyException If the provided parameters are inconsistent. * @throws CertificateSigningException If an error occurs during the signing process. * @since 0.1.0 */ - CalypsoCardCertificateV1 build(RSAPrivateKey issuerPrivateKey, byte[] issuerPublicKeyReference); - - /** - * Checks the consistency of the parameters, signs the certificate using the provided signer and - * returns a new instance of {@link CalypsoCardCertificateV1}. - * - * @param cardCertificateSigner The external signer for card certificate generation. - * @return A non-null reference. - * @throws IllegalArgumentException If the provided signer is null. - * @throws IllegalStateException If one of the required parameters is wrong or missing. - * @throws CertificateSigningException If an error occurs during the signing process. - * @since 0.1.0 - */ - CalypsoCardCertificateV1 build(CalypsoCardCertificateSignerSpi cardCertificateSigner); + CalypsoCardCertificateV1 build(); } diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/CalypsoCardCertificateSignerSpi.java b/src/main/java/org/eclipse/keypop/calypso/certificate/spi/CalypsoCertificateSignerSpi.java similarity index 69% rename from src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/CalypsoCardCertificateSignerSpi.java rename to src/main/java/org/eclipse/keypop/calypso/certificate/spi/CalypsoCertificateSignerSpi.java index 15ed3c5..b6233da 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/CalypsoCardCertificateSignerSpi.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/spi/CalypsoCertificateSignerSpi.java @@ -7,25 +7,17 @@ * * SPDX-License-Identifier: MIT ****************************************************************************** */ -package org.eclipse.keypop.calypso.certificate.card.spi; +package org.eclipse.keypop.calypso.certificate.spi; /** - * Signer for a Calypso card certificate. + * Signer for a Calypso certificate. * *

    Implementations of this interface provide the cryptographic signing functionality used to - * generate signed Calypso card certificates. + * generate signed Calypso CA and card certificates. * * @since 0.1.0 */ -public interface CalypsoCardCertificateSignerSpi { - - /** - * Returns the reference to the issuer's public key. - * - * @return A 29-byte byte array. - * @since 0.1.0 - */ - byte[] getIssuerPublicKeyReference(); +public interface CalypsoCertificateSignerSpi { /** * Generates a signed card certificate based on the provided data and recoverable data. @@ -34,8 +26,7 @@ public interface CalypsoCardCertificateSignerSpi { * @param recoverableData The byte array containing the recoverable data for the certificate. This * might be encrypted or protected data that shouldn't be included in the final certificate * but is needed for signature generation. - * @return The signed card certificate, a 316-byte byte array containing the data followed by the - * signature. + * @return The signed certificate, a byte array containing the data followed by the signature. * @since 0.1.0 */ byte[] generateSignedCertificate(byte[] data, byte[] recoverableData); diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/package-info.java b/src/main/java/org/eclipse/keypop/calypso/certificate/spi/package-info.java similarity index 50% rename from src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/package-info.java rename to src/main/java/org/eclipse/keypop/calypso/certificate/spi/package-info.java index b3efe25..4babe86 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/spi/package-info.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/spi/package-info.java @@ -1,6 +1,6 @@ /** - * SPIs to be implemented by end user applications related to Calypso card certificates signing. + * SPIs to be implemented by end user applications related to Calypso certificates signing. * * @since 0.1.0 */ -package org.eclipse.keypop.calypso.certificate.card.spi; +package org.eclipse.keypop.calypso.certificate.spi; From b60d41c47815b10ab6251f17cd7c5257a41fdfec Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Thu, 22 Feb 2024 19:34:31 +0100 Subject: [PATCH 07/13] wip --- ...a => CalypsoCaCertificateV1Generator.java} | 33 ++++---- ...=> CalypsoCardCertificateV1Generator.java} | 33 ++++---- .../CalypsoCertificateApiFactory.java | 52 ++----------- .../certificate/CalypsoCertificateStore.java | 76 ++----------------- .../certificate/ca/CalypsoCaCertificate.java | 28 ------- .../ca/CalypsoCaCertificateV1.java | 22 ------ .../calypso/certificate/ca/package-info.java | 6 -- .../ca/spi/CalypsoCaCertificateSignerSpi.java | 42 ---------- .../certificate/ca/spi/package-info.java | 6 -- .../card/CalypsoCardCertificate.java | 28 ------- .../card/CalypsoCardCertificateV1.java | 22 ------ .../certificate/card/package-info.java | 6 -- 12 files changed, 43 insertions(+), 311 deletions(-) rename src/main/java/org/eclipse/keypop/calypso/certificate/{ca/CalypsoCaCertificateV1Builder.java => CalypsoCaCertificateV1Generator.java} (85%) rename src/main/java/org/eclipse/keypop/calypso/certificate/{card/CalypsoCardCertificateV1Builder.java => CalypsoCardCertificateV1Generator.java} (78%) delete mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificate.java delete mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java delete mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/ca/package-info.java delete mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/CalypsoCaCertificateSignerSpi.java delete mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/package-info.java delete mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificate.java delete mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java delete mode 100644 src/main/java/org/eclipse/keypop/calypso/certificate/card/package-info.java diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCaCertificateV1Generator.java similarity index 85% rename from src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java rename to src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCaCertificateV1Generator.java index 10e8a20..15afd2f 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1Builder.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCaCertificateV1Generator.java @@ -7,19 +7,17 @@ * * SPDX-License-Identifier: MIT ****************************************************************************** */ -package org.eclipse.keypop.calypso.certificate.ca; +package org.eclipse.keypop.calypso.certificate; import java.security.interfaces.RSAPublicKey; -import org.eclipse.keypop.calypso.certificate.CertificateConsistencyException; -import org.eclipse.keypop.calypso.certificate.CertificateSigningException; /** - * Builds a {@link CalypsoCaCertificateV1} conforming to version 1 of the Calypso CA certificate + * Generates a certificate as a byte array conforming to version 1 of the Calypso CA certificate * format. * * @since 0.1.0 */ -public interface CalypsoCaCertificateV1Builder { +public interface CalypsoCaCertificateV1Generator { /** * Sets the public key of the CA. @@ -29,15 +27,15 @@ public interface CalypsoCaCertificateV1Builder { * *

    The associated reference is a 29-byte byte array. * - * @param caPublicKey The RSA public key of the CA (2048 bits, public exponent 65537). * @param caPublicKeyReference A 29-byte byte array representing a reference to the CA's public * key. + * @param caPublicKey The RSA public key of the CA (2048 bits, public exponent 65537). * @return The current instance. * @throws IllegalArgumentException If one of the provided argument is null or invalid. * @since 0.1.0 */ - CalypsoCaCertificateV1Builder withCaPublicKey( - RSAPublicKey caPublicKey, byte[] caPublicKeyReference); + CalypsoCaCertificateV1Generator withCaPublicKey( + byte[] caPublicKeyReference, RSAPublicKey caPublicKey); /** * Sets the start date of the validity period of the certificate's public key. @@ -55,7 +53,7 @@ CalypsoCaCertificateV1Builder withCaPublicKey( * @throws IllegalArgumentException If any date parameter is out of range. * @since 0.1.0 */ - CalypsoCaCertificateV1Builder withStartDate(int year, int month, int day); + CalypsoCaCertificateV1Generator withStartDate(int year, int month, int day); /** * Sets the end date of the validity period of the certificate's public key. @@ -73,7 +71,7 @@ CalypsoCaCertificateV1Builder withCaPublicKey( * @throws IllegalArgumentException If any date parameter is out of range. * @since 0.1.0 */ - CalypsoCaCertificateV1Builder withEndDate(int year, int month, int day); + CalypsoCaCertificateV1Generator withEndDate(int year, int month, int day); /** * Restricts certificate validity to cards whose Application Identifier (AID) begins with the @@ -101,7 +99,7 @@ CalypsoCaCertificateV1Builder withCaPublicKey( * zero bytes. * @since 0.1.0 */ - CalypsoCaCertificateV1Builder withAid(byte[] aid, boolean isTruncated); + CalypsoCaCertificateV1Generator withAid(byte[] aid, boolean isTruncated); /** * Sets the CA rights for this card certificate, controlling which types of certificates can be @@ -134,7 +132,7 @@ CalypsoCaCertificateV1Builder withCaPublicKey( * @throws IllegalArgumentException If the provided byte contains RFU values. * @since 0.1.0 */ - CalypsoCaCertificateV1Builder withCaRights(byte caRights); + CalypsoCaCertificateV1Generator withCaRights(byte caRights); /** * Sets the CA scope for this card certificate, defining the context in which the CA key pair can @@ -156,18 +154,17 @@ CalypsoCaCertificateV1Builder withCaPublicKey( * @throws IllegalArgumentException If the provided byte contains RFU values. * @since 0.1.0 */ - CalypsoCaCertificateV1Builder withCaScope(byte caScope); + CalypsoCaCertificateV1Generator withCaScope(byte caScope); /** - * Checks the consistency of the parameters, signs the certificate using the provided private key - * and returns a new instance of {@link CalypsoCaCertificateV1}. + * Checks the consistency of the parameters, signs the certificate using the provided signer and + * returns a byte array representing the certificate ready to be injected into a card. * - * @return A non-null reference. - * @throws IllegalArgumentException If one of the provided arguments is null. + * @return A 384-byte byte array. * @throws IllegalStateException If one of the required parameters is wrong or missing. * @throws CertificateConsistencyException If the provided parameters are inconsistent. * @throws CertificateSigningException If an error occurs during the signing process. * @since 0.1.0 */ - CalypsoCaCertificateV1 build(); + byte[] generate(); } diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCardCertificateV1Generator.java similarity index 78% rename from src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java rename to src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCardCertificateV1Generator.java index c6c44b1..e194ddc 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1Builder.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCardCertificateV1Generator.java @@ -7,18 +7,15 @@ * * SPDX-License-Identifier: MIT ****************************************************************************** */ -package org.eclipse.keypop.calypso.certificate.card; - -import org.eclipse.keypop.calypso.certificate.CertificateConsistencyException; -import org.eclipse.keypop.calypso.certificate.CertificateSigningException; +package org.eclipse.keypop.calypso.certificate; /** - * Builds a {@link CalypsoCardCertificateV1} conforming to version 1 of the Calypso card certificate + * Generates a certificate as a byte array conforming to version 1 of the Calypso card certificate * format. * * @since 0.1.0 */ -public interface CalypsoCardCertificateV1Builder { +public interface CalypsoCardCertificateV1Generator { /** * Sets the public key of the card, provided as a 64-byte array. @@ -32,7 +29,7 @@ public interface CalypsoCardCertificateV1Builder { * @throws IllegalArgumentException If the provided key is null or out of range. * @since 0.1.0 */ - CalypsoCardCertificateV1Builder withCardPublicKey(byte[] cardPublicKey); + CalypsoCardCertificateV1Generator withCardPublicKey(byte[] cardPublicKey); /** * Sets the start date of the validity period of the certificate's public key. @@ -50,7 +47,7 @@ public interface CalypsoCardCertificateV1Builder { * @throws IllegalArgumentException If any date parameter is out of range. * @since 0.1.0 */ - CalypsoCardCertificateV1Builder withStartDate(int year, int month, int day); + CalypsoCardCertificateV1Generator withStartDate(int year, int month, int day); /** * Sets the end date of the validity period of the certificate's public key. @@ -68,7 +65,7 @@ public interface CalypsoCardCertificateV1Builder { * @throws IllegalArgumentException If any date parameter is out of range. * @since 0.1.0 */ - CalypsoCardCertificateV1Builder withEndDate(int year, int month, int day); + CalypsoCardCertificateV1Generator withEndDate(int year, int month, int day); /** * Sets the AID of the autonomous PKI application of the target card. @@ -81,7 +78,7 @@ public interface CalypsoCardCertificateV1Builder { * zero bytes. * @since 0.1.0 */ - CalypsoCardCertificateV1Builder withAid(byte[] aid); + CalypsoCardCertificateV1Generator withCardAid(byte[] aid); /** * Sets the serial number of the card for which the certificate is being generated. @@ -91,7 +88,7 @@ public interface CalypsoCardCertificateV1Builder { * @throws IllegalArgumentException If the provided argument is null or out of range. * @since 0.1.0 */ - CalypsoCardCertificateV1Builder withCardSerialNumber(byte[] serialNumber); + CalypsoCardCertificateV1Generator withCardSerialNumber(byte[] serialNumber); /** * Sets the startup info of the card for which the certificate is being generated. @@ -102,7 +99,7 @@ public interface CalypsoCardCertificateV1Builder { * @throws IllegalArgumentException If the provided argument is null or out of range. * @since 0.1.0 */ - CalypsoCardCertificateV1Builder withCardStartupInfo(byte[] startupInfo); + CalypsoCardCertificateV1Generator withCardStartupInfo(byte[] startupInfo); /** * Sets the index used to differentiate two card certificates generated with the same issuer @@ -114,19 +111,17 @@ public interface CalypsoCardCertificateV1Builder { * @return The current instance. * @since 0.1.0 */ - CalypsoCardCertificateV1Builder withIndex(int index); + CalypsoCardCertificateV1Generator withIndex(int index); /** - * Checks the consistency of the parameters, signs the certificate either using the provided - * private key or the provided signer and returns a new instance of {@link - * CalypsoCardCertificateV1}. + * Checks the consistency of the parameters, signs the certificate using the provided signer and + * returns a byte array representing the certificate ready to be injected into a card. * - * @return A non-null reference. - * @throws IllegalArgumentException If one of the provided arguments is null. + * @return A 316-byte byte array. * @throws IllegalStateException If one of the required parameters is wrong or missing. * @throws CertificateConsistencyException If the provided parameters are inconsistent. * @throws CertificateSigningException If an error occurs during the signing process. * @since 0.1.0 */ - CalypsoCardCertificateV1 build(); + byte[] generate(); } diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java index 1a68741..a591519 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java @@ -9,8 +9,6 @@ ****************************************************************************** */ package org.eclipse.keypop.calypso.certificate; -import org.eclipse.keypop.calypso.certificate.ca.CalypsoCaCertificateV1Builder; -import org.eclipse.keypop.calypso.certificate.card.CalypsoCardCertificateV1Builder; import org.eclipse.keypop.calypso.certificate.spi.CalypsoCertificateSignerSpi; /** @@ -36,28 +34,9 @@ public interface CalypsoCertificateApiFactory { CalypsoCertificateStore getCalypsoCertificateStore(); /** - * Creates a new builder for Calypso CA certificates (version 1) using the internal signer. + * Creates a new generator for Calypso CA certificates (version 1) using an external signer. * - *

    The builder is used to configure and build signed Calypso CA certificates. - * - *

    This method must be called after the issuer certificate and its associated private key have - * been injected into the store using the specified issuer public key reference. - * - * @param issuerPublicKeyReference The reference to issuer's public key in the store. - * @throws IllegalStateException If the provided reference is unknown or the issuer's private key - * is missing. - * @throws CertificateConsistencyException If the provided reference doesn't designate a valid - * certification for the operation. - * @return A non-null reference. - * @since 0.1.0 - */ - CalypsoCaCertificateV1Builder createCalypsoCaCertificateV1Builder( - byte[] issuerPublicKeyReference); - - /** - * Creates a new builder for Calypso CA certificates (version 1) using an external signer. - * - *

    The builder is used to configure and build signed Calypso CA certificates. + *

    The generator is used to configure and generate signed Calypso CA certificates. * *

    This method must be called after the issuer certificate of the signer using the specified * issuer public key reference. @@ -70,32 +49,13 @@ CalypsoCaCertificateV1Builder createCalypsoCaCertificateV1Builder( * @return A non-null reference. * @since 0.1.0 */ - CalypsoCaCertificateV1Builder createCalypsoCaCertificateV1Builder( + CalypsoCaCertificateV1Generator createCalypsoCaCertificateV1Generator( byte[] issuerPublicKeyReference, CalypsoCertificateSignerSpi caCertificateSigner); /** - * Creates a new builder for Calypso card certificates (version 1) using the internal signer. - * - *

    The builder is used to configure and build signed Calypso card certificates. - * - *

    This method must be called after the issuer certificate and its associated private key have - * been injected into the store using the specified issuer public key reference. - * - * @param issuerPublicKeyReference The reference to issuer's public key in the store. - * @throws IllegalStateException If the provided reference is unknown or the issuer's private key - * is missing. - * @throws CertificateConsistencyException If the provided reference doesn't designate a valid - * certification for the operation. - * @return A non-null reference. - * @since 0.1.0 - */ - CalypsoCardCertificateV1Builder createCalypsoCardCertificateV1Builder( - byte[] issuerPublicKeyReference); - - /** - * Creates a new builder for Calypso card certificates (version 1) using an external signer. + * Creates a new generator for Calypso card certificates (version 1) using an external signer. * - *

    The builder is used to configure and build signed Calypso card certificates. + *

    The generator is used to configure and generate signed Calypso card certificates. * *

    This method must be called after the issuer certificate of the signer using the specified * issuer public key reference. @@ -108,6 +68,6 @@ CalypsoCardCertificateV1Builder createCalypsoCardCertificateV1Builder( * @return A non-null reference. * @since 0.1.0 */ - CalypsoCardCertificateV1Builder createCalypsoCardCertificateV1Builder( + CalypsoCardCertificateV1Generator createCalypsoCardCertificateV1Generator( byte[] issuerPublicKeyReference, CalypsoCertificateSignerSpi cardCertificateSigner); } diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java index 594ddce..345d184 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java @@ -9,20 +9,10 @@ ****************************************************************************** */ package org.eclipse.keypop.calypso.certificate; -import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; -import org.eclipse.keypop.calypso.certificate.ca.CalypsoCaCertificate; /** - * Provides a store for managing Calypso Certificate Authority (CA) certificates and keys. - * - *

    This interface offers methods for adding the following to the store: - * - *

      - *
    • PCA public keys and key pairs - *
    • Calypso CA certificates - *
    • Calypso CA certificates with their associated private keys - *
    + * Provides a store for managing Calypso Certificate Authority (CA) certificates and public keys. * *

    The stored certificates and keys are used for verifying and creating Calypso CA certificates * within the certificate infrastructure. @@ -44,48 +34,20 @@ public interface CalypsoCertificateStore { *

    The provided public key reference will be used for identifying the key when creating * certificates within the certificate infrastructure. * - *

    This method is typically used for scenarios where the need is to verify Calypso CA - * certificates. By providing both the public and private keys, the store has the necessary - * information for these operations. - * *

    The key reference is to be used when creating certificates according to the desired * infrastructure. * *

    This method is suitable for providing means to verify CA certificates. * - * @param publicKeyReference The reference to the public key. - * @param publicKey The public key. + * @param pcaPublicKeyReference The reference to the PCA public key. + * @param pcaPublicKey The PCA public key. * @return The current instance. * @throws IllegalArgumentException If one of the argument is null or if the key is not a 2048-bit * RSA key. + * @throws IllegalStateException If the reference to the public key already in the store. * @since 0.1.0 */ - CalypsoCertificateStore addPcaPublicKey(byte[] publicKeyReference, RSAPublicKey publicKey); - - /** - * Adds a Primary Certification Authority (PCA) public key, its reference, and the associated - * private key. - * - *

    This method expects a 2048-bit RSA key with an exponent of 65537. Both the public part of - * the key and its associated private part must be provided. - * - *

    The provided public key reference will be used for identifying the key when creating - * certificates within the certificate infrastructure. - * - *

    This method is typically used for scenarios where the need is to verify and/or create - * Calypso CA certificates. By providing both the public and private keys, the store has the - * necessary information for these operations. - * - * @param publicKeyReference The reference to the public key. - * @param publicKey The public key. - * @param privateKey The associated private key corresponding to the provided public key. - * @return The current instance. - * @throws IllegalArgumentException If one of the argument is null or if the provided key pair is - * not a valid 2048-bit RSA key pair. - * @since 0.1.0 - */ - CalypsoCertificateStore addPcaKeyPair( - byte[] publicKeyReference, RSAPublicKey publicKey, RSAPrivateKey privateKey); + CalypsoCertificateStore addPcaPublicKey(byte[] pcaPublicKeyReference, RSAPublicKey pcaPublicKey); /** * Adds a Calypso Certificate Authority (CA) certificate to the store. @@ -97,34 +59,12 @@ CalypsoCertificateStore addPcaKeyPair( *

    This method is typically used to add an intermediate CA certificates necessary for * validating other certificates within the infrastructure. * - * @param caCertificate The Calypso CA certificate to add. + * @param caCertificate A 384-byte byte array containing the Calypso CA certificate to add. * @return The current instance. * @throws IllegalArgumentException If the certificate is null. + * @throws IllegalStateException If the reference to the public key already in the store. * @throws CertificateConsistencyException If the certificate is not trusted. * @since 0.1.0 */ - CalypsoCertificateStore addCalypsoCaCertificate(CalypsoCaCertificate caCertificate); - - /** - * Adds a Calypso Certificate Authority (CA) certificate and its associated private key to the - * store. - * - *

    This method adds the provided certificate and its associated private key to the store. Both - * the certificate must be valid (signed by an already referenced authority) and the private key - * must correspond to the certificate. - * - *

    This method is typically used for scenarios where you need to issue Calypso CA or card - * certificates. By providing both the certificate and its private key, the store has the - * necessary information to sign new certificates. - * - * @param caCertificate The Calypso CA certificate to add. - * @param caPrivateKey The private key associated with the provided certificate. - * @return The current instance. - * @throws IllegalArgumentException If either the certificate or the private key is null or - * invalid, or if they do not correspond to each other. - * @throws CertificateConsistencyException If the certificate is not trusted. - * @since 0.1.0 - */ - CalypsoCertificateStore addCalypsoCaCertificate( - CalypsoCaCertificate caCertificate, RSAPrivateKey caPrivateKey); + CalypsoCertificateStore addCalypsoCaCertificate(byte[] caCertificate); } diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificate.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificate.java deleted file mode 100644 index 55f9275..0000000 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificate.java +++ /dev/null @@ -1,28 +0,0 @@ -/* ****************************************************************************** - * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ - * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. - * - * SPDX-License-Identifier: MIT - ****************************************************************************** */ -package org.eclipse.keypop.calypso.certificate.ca; - -import org.eclipse.keypop.calypso.card.transaction.spi.CaCertificate; - -/** - * CA Certificate compliant with the 384-byte format supported by the Calypso cards. - * - * @since 0.1.0 - */ -public interface CalypsoCaCertificate extends CaCertificate { - - /** - * Returns a byte array corresponding to the certificate as it is stored in the card. - * - * @return A 384-byte byte array. - * @since 0.1.0 - */ - byte[] getRawData(); -} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java deleted file mode 100644 index a1bd824..0000000 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/CalypsoCaCertificateV1.java +++ /dev/null @@ -1,22 +0,0 @@ -/* ****************************************************************************** - * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ - * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. - * - * SPDX-License-Identifier: MIT - ****************************************************************************** */ -package org.eclipse.keypop.calypso.certificate.ca; - -import org.eclipse.keypop.calypso.card.transaction.spi.CaCertificate; - -/** - * A Calypso CA certificate version 1. - * - *

    It can be used for certificate generation and card transaction security settings. - * - * @see CaCertificate - * @since 0.1.0 - */ -public interface CalypsoCaCertificateV1 extends CalypsoCaCertificate {} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/package-info.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/package-info.java deleted file mode 100644 index e839c6f..0000000 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Interfaces related to Calypso CA certificates. - * - * @since 0.1.0 - */ -package org.eclipse.keypop.calypso.certificate.ca; diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/CalypsoCaCertificateSignerSpi.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/CalypsoCaCertificateSignerSpi.java deleted file mode 100644 index 6f0e4e0..0000000 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/CalypsoCaCertificateSignerSpi.java +++ /dev/null @@ -1,42 +0,0 @@ -/* ****************************************************************************** - * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ - * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. - * - * SPDX-License-Identifier: MIT - ****************************************************************************** */ -package org.eclipse.keypop.calypso.certificate.ca.spi; - -/** - * Signer for a Calypso CA certificate. - * - *

    Implementations of this interface provide the cryptographic signing functionality used to - * generate signed Calypso CA certificates. - * - * @since 0.1.0 - */ -public interface CalypsoCaCertificateSignerSpi { - - /** - * Returns the reference to the issuer's public key. - * - * @return A 29-byte byte array. - * @since 0.1.0 - */ - byte[] getIssuerPublicKeyReference(); - - /** - * Generates a signed CA certificate based on the provided data and recoverable data. - * - * @param data The byte array containing the non-recoverable data for the certificate. - * @param recoverableData The byte array containing the recoverable data for the certificate. This - * might be encrypted or protected data that shouldn't be included in the final certificate - * but is needed for signature generation. - * @return The signed CA certificate, a 384-byte byte array containing the data followed by the - * signature. - * @since 0.1.0 - */ - byte[] generateSignedCertificate(byte[] data, byte[] recoverableData); -} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/package-info.java b/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/package-info.java deleted file mode 100644 index 455d880..0000000 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/ca/spi/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * SPIs to be implemented by end user applications related to Calypso CA certificates signing. - * - * @since 0.1.0 - */ -package org.eclipse.keypop.calypso.certificate.ca.spi; diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificate.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificate.java deleted file mode 100644 index 519f0e1..0000000 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificate.java +++ /dev/null @@ -1,28 +0,0 @@ -/* ****************************************************************************** - * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ - * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. - * - * SPDX-License-Identifier: MIT - ****************************************************************************** */ -package org.eclipse.keypop.calypso.certificate.card; - -import org.eclipse.keypop.calypso.card.transaction.spi.CaCertificate; - -/** - * Card Certificate compliant with the 316-byte format supported by the Calypso cards. - * - * @since 0.1.0 - */ -public interface CalypsoCardCertificate extends CaCertificate { - - /** - * Returns a byte array corresponding to the certificate as it is stored in the card. - * - * @return A 316-byte byte array. - * @since 0.1.0 - */ - byte[] getRawData(); -} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java deleted file mode 100644 index 9a5f2e9..0000000 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/CalypsoCardCertificateV1.java +++ /dev/null @@ -1,22 +0,0 @@ -/* ****************************************************************************** - * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ - * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. - * - * SPDX-License-Identifier: MIT - ****************************************************************************** */ -package org.eclipse.keypop.calypso.certificate.card; - -import org.eclipse.keypop.calypso.card.transaction.spi.CardCertificate; - -/** - * A Calypso card certificate version 1. - * - *

    It can be used for certificate generation and external certificate validation. - * - * @see CardCertificate - * @since 0.1.0 - */ -public interface CalypsoCardCertificateV1 extends CalypsoCardCertificate {} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/card/package-info.java b/src/main/java/org/eclipse/keypop/calypso/certificate/card/package-info.java deleted file mode 100644 index 3239034..0000000 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/card/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Interfaces related to Calypso card certificates. - * - * @since 0.1.0 - */ -package org.eclipse.keypop.calypso.certificate.card; From da1dcc81e86c53d38ec18d63a9e1eee2eae20330 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Sun, 25 Feb 2024 18:31:10 +0100 Subject: [PATCH 08/13] wip --- build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index d3ab0b0..22a2101 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,7 +24,6 @@ repositories { mavenCentral() } dependencies { - implementation("org.eclipse.keypop:keypop-calypso-card-java-api:2.1.0-SNAPSHOT") { isChanging = true } testImplementation("junit:junit:4.13.2") testImplementation("org.assertj:assertj-core:3.15.0") } From 6021a9992c3f9793692e4736c0e227ff3c3b980d Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Tue, 5 Mar 2024 17:22:53 +0100 Subject: [PATCH 09/13] wip --- .../certificate/CalypsoCertificateStore.java | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java index 345d184..3843a32 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java @@ -49,6 +49,30 @@ public interface CalypsoCertificateStore { */ CalypsoCertificateStore addPcaPublicKey(byte[] pcaPublicKeyReference, RSAPublicKey pcaPublicKey); + /** + * Adds a Primary Certification Authority (PCA) public key from it modulus and its reference. + * + *

    This method expects the 256-byte modulus of a 2048-bit RSA public key with an exponent of + * 65537. + * + *

    The provided public key reference will be used for identifying the key when creating + * certificates within the certificate infrastructure. + * + *

    The key reference is to be used when creating certificates according to the desired + * infrastructure. + * + *

    This method is suitable for providing means to verify CA certificates. + * + * @param pcaPublicKeyReference The reference to the PCA public key. + * @param pcaPublicKeyModulus The modulus of the PCA public key as a 256-byte byte array. + * @return The current instance. + * @throws IllegalArgumentException If one of the argument is null or if the key modulus is not + * 256 bytes long. + * @throws IllegalStateException If the reference to the public key already in the store. + * @since 0.1.0 + */ + CalypsoCertificateStore addPcaPublicKey(byte[] pcaPublicKeyReference, byte[] pcaPublicKeyModulus); + /** * Adds a Calypso Certificate Authority (CA) certificate to the store. * @@ -61,7 +85,7 @@ public interface CalypsoCertificateStore { * * @param caCertificate A 384-byte byte array containing the Calypso CA certificate to add. * @return The current instance. - * @throws IllegalArgumentException If the certificate is null. + * @throws IllegalArgumentException If the certificate is null or its format is unknown. * @throws IllegalStateException If the reference to the public key already in the store. * @throws CertificateConsistencyException If the certificate is not trusted. * @since 0.1.0 From 329ff86f107934936bcf0c84658fca74fc999ae8 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Fri, 8 Mar 2024 16:30:04 +0100 Subject: [PATCH 10/13] wip --- .../calypso/certificate/CertificateConsistencyException.java | 2 +- .../keypop/calypso/certificate/CertificateSigningException.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateConsistencyException.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateConsistencyException.java index a0eeab8..4fdeaf9 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateConsistencyException.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateConsistencyException.java @@ -10,7 +10,7 @@ package org.eclipse.keypop.calypso.certificate; /** - * Exception that is thrown when an error occurs during the certificate validation process. + * Indicated that an error occurred during the certificate validation process. * * @since 0.1.0 */ diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateSigningException.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateSigningException.java index 59e3507..ee92184 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateSigningException.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateSigningException.java @@ -10,7 +10,7 @@ package org.eclipse.keypop.calypso.certificate; /** - * Exception that is thrown when an error occurs during the certificate signing process. + * Indicates that an error occurred during the certificate signing process. * * @since 0.1.0 */ From 21c0e301e64abb8190da9cc9b821991b4a65542d Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Sat, 16 Mar 2024 21:05:12 +0100 Subject: [PATCH 11/13] wip --- .../certificate/CalypsoCertificateStore.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java index 3843a32..56592b3 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java @@ -41,13 +41,12 @@ public interface CalypsoCertificateStore { * * @param pcaPublicKeyReference The reference to the PCA public key. * @param pcaPublicKey The PCA public key. - * @return The current instance. * @throws IllegalArgumentException If one of the argument is null or if the key is not a 2048-bit * RSA key. * @throws IllegalStateException If the reference to the public key already in the store. * @since 0.1.0 */ - CalypsoCertificateStore addPcaPublicKey(byte[] pcaPublicKeyReference, RSAPublicKey pcaPublicKey); + void addPcaPublicKey(byte[] pcaPublicKeyReference, RSAPublicKey pcaPublicKey); /** * Adds a Primary Certification Authority (PCA) public key from it modulus and its reference. @@ -65,16 +64,17 @@ public interface CalypsoCertificateStore { * * @param pcaPublicKeyReference The reference to the PCA public key. * @param pcaPublicKeyModulus The modulus of the PCA public key as a 256-byte byte array. - * @return The current instance. * @throws IllegalArgumentException If one of the argument is null or if the key modulus is not * 256 bytes long. * @throws IllegalStateException If the reference to the public key already in the store. + * @throws CertificateConsistencyException If the certificate is not trusted. * @since 0.1.0 */ - CalypsoCertificateStore addPcaPublicKey(byte[] pcaPublicKeyReference, byte[] pcaPublicKeyModulus); + void addPcaPublicKey(byte[] pcaPublicKeyReference, byte[] pcaPublicKeyModulus); /** - * Adds a Calypso Certificate Authority (CA) certificate to the store. + * Adds a Calypso Certificate Authority (CA) certificate to the store and returns its public key + * reference. * *

    This method adds the provided certificate to the store. The certificate must be valid * (signed by an already referenced authority) and issued by a trusted authority previously @@ -84,11 +84,12 @@ public interface CalypsoCertificateStore { * validating other certificates within the infrastructure. * * @param caCertificate A 384-byte byte array containing the Calypso CA certificate to add. - * @return The current instance. + * @return The public key reference of the added certificate. * @throws IllegalArgumentException If the certificate is null or its format is unknown. - * @throws IllegalStateException If the reference to the public key already in the store. + * @throws IllegalStateException If the reference to the public key already in the store or the + * parent certificate was not found. * @throws CertificateConsistencyException If the certificate is not trusted. * @since 0.1.0 */ - CalypsoCertificateStore addCalypsoCaCertificate(byte[] caCertificate); + byte[] addCalypsoCaCertificate(byte[] caCertificate); } From feb0180014a43de2e8bdf5964d4bd289ef56e537 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Fortune Date: Mon, 1 Dec 2025 14:04:43 +0100 Subject: [PATCH 12/13] refactor: remove legacy scripts and update configs --- .github/workflows/build-and-test.yml | 9 + .github/workflows/publish-release.yml | 10 + .github/workflows/publish-snapshot.yml | 10 + .gitignore | 1 - CONTRIBUTING.md | 2 +- Jenkinsfile | 83 ------ LICENSE_HEADER | 11 + NOTICE.md | 4 +- README.md | 2 +- build.gradle.kts | 206 +++++++++++--- gradle.properties | 42 ++- gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 61624 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 269 +++++++++++------- gradlew.bat | 15 +- scripts/check_version.sh | 12 - scripts/prepare_javadoc.sh | 52 ---- settings.gradle.kts | 17 +- .../CalypsoCaCertificateV1Generator.java | 12 +- .../CalypsoCardCertificateV1Generator.java | 12 +- .../CalypsoCertificateApiFactory.java | 12 +- .../CalypsoCertificateApiProperties.java | 12 +- .../certificate/CalypsoCertificateStore.java | 12 +- .../CertificateConsistencyException.java | 12 +- .../CertificateSigningException.java | 12 +- .../spi/CalypsoCertificateSignerSpi.java | 12 +- .../CalypsoCertificateApiPropertiesTest.java | 18 +- 27 files changed, 505 insertions(+), 357 deletions(-) create mode 100644 .github/workflows/build-and-test.yml create mode 100644 .github/workflows/publish-release.yml create mode 100644 .github/workflows/publish-snapshot.yml delete mode 100644 Jenkinsfile create mode 100644 LICENSE_HEADER delete mode 100644 scripts/check_version.sh delete mode 100644 scripts/prepare_javadoc.sh diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..50885d2 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,9 @@ +name: Build and Test + +on: + pull_request: + branches: [main] + +jobs: + build-and-test: + uses: eclipse-keypop/keypop-actions/.github/workflows/reusable-build-and-test.yml@main # NOSONAR - Same organization, trusted source diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000..b4776c4 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,10 @@ +name: Publish Release package + +on: + release: + types: [published] + +jobs: + publish-release: + uses: eclipse-keypop/keypop-actions/.github/workflows/reusable-publish-release.yml@main # NOSONAR - Same organization, trusted source + secrets: inherit # NOSONAR - Same organization, trusted source diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml new file mode 100644 index 0000000..85cdaa5 --- /dev/null +++ b/.github/workflows/publish-snapshot.yml @@ -0,0 +1,10 @@ +name: Publish Snapshot package + +on: + push: + branches: [main] + +jobs: + publish-snapshot: + uses: eclipse-keypop/keypop-actions/.github/workflows/reusable-publish-snapshot.yml@main # NOSONAR - Same organization, trusted source + secrets: inherit # NOSONAR - Same organization, trusted source diff --git a/.gitignore b/.gitignore index 01ddf38..fc74077 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,6 @@ release/ # Gradle .gradle/ build/ -LICENSE_HEADER # Eclipse .classpath diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4609d01..e20bf5e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,7 +9,7 @@ Keypop: ## Quick Start 1. **Read the Contribution Guidelines**: Before starting, please visit our detailed contributing guide at the -[Eclipse Keypop Contribution Guide](https://eclipse-keypop.github.io/keypop-website/community/contributing/). +[Eclipse Keypop Contribution Guide](https://keypop.org/community/contributing/). This guide covers all the necessary information about contributing to the project. 2. **Join the mailing list**: Connect with other contributors on our diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 012a641..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,83 +0,0 @@ -#!groovy -pipeline { - environment { - PROJECT_NAME = "keypop-calypso-certificate-java-api" - PROJECT_BOT_NAME = "Eclipse Keypop Bot" - } - agent { kubernetes { yaml javaBuilder('1.0') } } - stages { - stage('Import keyring') { - when { expression { env.GIT_URL.startsWith('https://github.com/eclipse-keypop/keypop-') && env.CHANGE_ID == null } } - steps { container('java-builder') { - withCredentials([file(credentialsId: 'secret-subkeys.asc', variable: 'KEYRING')]) { sh 'import_gpg "${KEYRING}"' } - } } - } - stage('Prepare settings') { steps { container('java-builder') { - script { - env.KEYPOP_VERSION = sh(script: 'grep version gradle.properties | cut -d= -f2 | tr -d "[:space:]"', returnStdout: true).trim() - env.GIT_COMMIT_MESSAGE = sh(script: 'git log --format=%B -1 | head -1 | tr -d "\n"', returnStdout: true) - echo "Building version ${env.KEYPOP_VERSION} in branch ${env.GIT_BRANCH}" - deployRelease = env.GIT_URL == "https://github.com/eclipse-keypop/${env.PROJECT_NAME}.git" && (env.GIT_BRANCH == "main" || env.GIT_BRANCH == "release-${env.KEYPOP_VERSION}") && env.CHANGE_ID == null && env.GIT_COMMIT_MESSAGE.startsWith("Release ${env.KEYPOP_VERSION}") - deploySnapshot = !deployRelease && env.GIT_URL == "https://github.com/eclipse-keypop/${env.PROJECT_NAME}.git" && (env.GIT_BRANCH == "main" || env.GIT_BRANCH == "release-${env.KEYPOP_VERSION}") && env.CHANGE_ID == null - } - sh "chmod +x ./gradlew ./scripts/*.sh" - } } } - stage('Check version') { - steps { container('java-builder') { - sh "./scripts/check_version.sh ${env.KEYPOP_VERSION}" - } } - } - stage('Build and Test') { - when { expression { !deploySnapshot && !deployRelease } } - steps { container('java-builder') { - sh './gradlew clean build test --no-build-cache --info --stacktrace' - junit testResults: 'build/test-results/test/*.xml', allowEmptyResults: true - } } - } - stage('Build and Publish Snapshot') { - when { expression { deploySnapshot } } - steps { container('java-builder') { - configFileProvider([configFile(fileId: 'gradle.properties', targetLocation: '/home/jenkins/agent/gradle.properties')]) { - sh './gradlew clean build test publish --info --stacktrace' - } - junit testResults: 'build/test-results/test/*.xml', allowEmptyResults: true - } } - } - stage('Build and Publish Release') { - when { expression { deployRelease } } - steps { container('java-builder') { - configFileProvider([configFile(fileId: 'gradle.properties', targetLocation: '/home/jenkins/agent/gradle.properties')]) { - sh './gradlew clean build test release --info --stacktrace' - } - junit testResults: 'build/test-results/test/*.xml', allowEmptyResults: true - } } - } - stage('Update GitHub Pages') { - when { expression { deploySnapshot || deployRelease } } - steps { container('java-builder') { - sh "./scripts/prepare_javadoc.sh ${env.PROJECT_NAME} ${env.KEYPOP_VERSION} ${deploySnapshot}" - dir("${env.PROJECT_NAME}") { - withCredentials([usernamePassword(credentialsId: 'github-bot', passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME')]) { - sh ''' - git add -A - git config user.email "${PROJECT_NAME}-bot@eclipse.org" - git config user.name "${PROJECT_BOT_NAME}" - git commit --allow-empty -m "docs: update documentation ${JOB_NAME}-${BUILD_NUMBER}" - git log --graph --abbrev-commit --date=relative -n 5 - git push "https://${GIT_USERNAME}:${GIT_PASSWORD}@github.com/eclipse-keypop/${PROJECT_NAME}.git" HEAD:gh-pages - ''' - } - } - } } - } - stage('Publish packaging to Eclipse') { - when { expression { deploySnapshot || deployRelease } } - steps { container('java-builder') { sshagent(['projects-storage.eclipse.org-bot-ssh']) { sh 'publish_packaging' } } } - } - } - post { always { container('java-builder') { - archiveArtifacts artifacts: 'build*/libs/**', allowEmptyArchive: true - archiveArtifacts artifacts: 'build*/reports/tests/**', allowEmptyArchive: true - archiveArtifacts artifacts: 'build*/reports/jacoco/test/html/**', allowEmptyArchive: true - } } } -} diff --git a/LICENSE_HEADER b/LICENSE_HEADER new file mode 100644 index 0000000..efc0ba1 --- /dev/null +++ b/LICENSE_HEADER @@ -0,0 +1,11 @@ +/* ************************************************************************************** + * Copyright (c) $YEAR Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * MIT License which is available at https://opensource.org/licenses/MIT + * + * SPDX-License-Identifier: MIT + ************************************************************************************** */ \ No newline at end of file diff --git a/NOTICE.md b/NOTICE.md index 2a51d9e..6d3755f 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -6,8 +6,8 @@ This content is produced and maintained by the Eclipse Keypop project. ## Supported platforms -* Java SE 1.6 compact2 -* Android 4.4 KitKat API level 19 +* Java 1.8 +* Android 7.0 Nougat API Level 24 ## Trademarks diff --git a/README.md b/README.md index 3cf4eb3..b0c7e5e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ to create Calypso certificates. ## Documentation & Contribution Guide The full documentation, including the **user guide**, **download information** and **contribution guide**, is available -on the Keypop website [keypop.org](https://eclipse-keypop.github.io/keypop-website/). +on the Keypop website [keypop.org](https://keypop.org/). ## API documentation diff --git a/build.gradle.kts b/build.gradle.kts index 22a2101..29814c0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,59 +1,193 @@ /////////////////////////////////////////////////////////////////////////////// // GRADLE CONFIGURATION /////////////////////////////////////////////////////////////////////////////// + plugins { - java - id("com.diffplug.spotless") version "5.10.2" -} -buildscript { - repositories { - mavenLocal() - mavenCentral() - } - dependencies { - classpath("org.eclipse.keypop:keypop-gradle:0.1.+") { isChanging = true } - } + java + `maven-publish` + signing + id("com.diffplug.spotless") version "6.25.0" } -apply(plugin = "org.eclipse.keypop") /////////////////////////////////////////////////////////////////////////////// // APP CONFIGURATION /////////////////////////////////////////////////////////////////////////////// -repositories { - mavenLocal() - mavenCentral() -} + dependencies { - testImplementation("junit:junit:4.13.2") - testImplementation("org.assertj:assertj-core:3.15.0") + testImplementation(platform("org.junit:junit-bom:5.12.2")) + testImplementation("org.junit.jupiter:junit-jupiter") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + testImplementation("org.assertj:assertj-core:3.25.3") +} + +/////////////////////////////////////////////////////////////////////////////// +// STANDARD CONFIGURATION FOR JAVA PROJECTS +/////////////////////////////////////////////////////////////////////////////// + +if (project.hasProperty("releaseTag")) { + project.version = project.property("releaseTag") as String + println("Release mode: version set to ${project.version}") +} else { + println("Development mode: version is ${project.version}") } val javaSourceLevel: String by project val javaTargetLevel: String by project + java { - sourceCompatibility = JavaVersion.toVersion(javaSourceLevel) - targetCompatibility = JavaVersion.toVersion(javaTargetLevel) - println("Compiling Java $sourceCompatibility to Java $targetCompatibility.") - withJavadocJar() - withSourcesJar() + sourceCompatibility = JavaVersion.toVersion(javaSourceLevel) + targetCompatibility = JavaVersion.toVersion(javaTargetLevel) + println("Compiling Java $sourceCompatibility to Java $targetCompatibility.") + withJavadocJar() + withSourcesJar() +} + +fun copyLicenseFiles() { + val metaInfDir = File(layout.buildDirectory.get().asFile, "resources/main/META-INF") + val licenseFile = File(project.rootDir, "LICENSE") + val noticeFile = File(project.rootDir, "NOTICE.md") + metaInfDir.mkdirs() + licenseFile.copyTo(File(metaInfDir, "LICENSE"), overwrite = true) + noticeFile.copyTo(File(metaInfDir, "NOTICE.md"), overwrite = true) } -/////////////////////////////////////////////////////////////////////////////// -// TASKS CONFIGURATION -/////////////////////////////////////////////////////////////////////////////// tasks { - spotless { - java { - target("src/**/*.java") - licenseHeaderFile("${project.rootDir}/LICENSE_HEADER") - importOrder("java", "javax", "org", "com", "") - removeUnusedImports() - googleJavaFormat() + spotless { + java { + target("src/**/*.java") + licenseHeaderFile("${project.rootDir}/LICENSE_HEADER") + importOrder("java", "javax", "org", "com", "") + removeUnusedImports() + googleJavaFormat() + } + kotlinGradle { + target("**/*.kts") + ktfmt() + } + } + test { + useJUnitPlatform() + testLogging { events("passed", "skipped", "failed") } + } + javadoc { + dependsOn(processResources) + val javadocLogo = project.findProperty("javadoc.logo") as String + val javadocCopyright = project.findProperty("javadoc.copyright") as String + val titleProperty = project.findProperty("title") as String + (options as StandardJavadocDocletOptions).apply { + overview = "src/main/javadoc/overview.html" + windowTitle = "$titleProperty - ${project.version}" + header( + "

    $javadocLogo $titleProperty - ${project.version}
    ") + docTitle("$titleProperty - ${project.version}") + use(true) + bottom(javadocCopyright) + encoding = "UTF-8" + charSet = "UTF-8" + if (JavaVersion.current().isJava11Compatible) { + addBooleanOption("html5", true) + addStringOption("Xdoclint:none", "-quiet") + } + } + doFirst { println("Generating Javadoc for ${project.name} version ${project.version}") } + } + jar { + dependsOn(processResources) + doFirst { copyLicenseFiles() } + manifest { + attributes( + mapOf( + "Implementation-Title" to (project.findProperty("title") as String), + "Implementation-Version" to project.version, + "Implementation-Vendor" to (project.findProperty("organization.name") as String), + "Implementation-URL" to (project.findProperty("project.url") as String), + "Specification-Title" to (project.findProperty("title") as String), + "Specification-Version" to project.version, + "Specification-Vendor" to (project.findProperty("organization.name") as String), + "Created-By" to + "${System.getProperty("java.version")} (${System.getProperty("java.vendor")})", + "Build-Jdk" to System.getProperty("java.version"))) + } + } + named("sourcesJar") { + doFirst { copyLicenseFiles() } + manifest { + attributes( + mapOf( + "Implementation-Title" to "${project.findProperty("title") as String} Sources", + "Implementation-Version" to project.version)) + } + } + named("javadocJar") { + dependsOn(javadoc) + doFirst { copyLicenseFiles() } + manifest { + attributes( + mapOf( + "Implementation-Title" to "${project.findProperty("title") as String} Documentation", + "Implementation-Version" to project.version)) + } + } +} + +publishing { + publications { + create("mavenJava") { + from(components["java"]) + pom { + name.set(project.findProperty("title") as String) + description.set(project.findProperty("description") as String) + url.set(project.findProperty("project.url") as String) + licenses { + license { + name.set(project.findProperty("license.name") as String) + url.set(project.findProperty("license.url") as String) + distribution.set(project.findProperty("license.distribution") as String) + } + } + developers { + developer { + name.set(project.findProperty("developer.name") as String) + email.set(project.findProperty("developer.email") as String) + } + } + organization { + name.set(project.findProperty("organization.name") as String) + url.set(project.findProperty("organization.url") as String) + } + scm { + connection.set(project.findProperty("scm.connection") as String) + developerConnection.set(project.findProperty("scm.developerConnection") as String) + url.set(project.findProperty("scm.url") as String) } + ciManagement { + system.set(project.findProperty("ci.system") as String) + url.set(project.findProperty("ci.url") as String) + } + properties.set( + mapOf( + "project.build.sourceEncoding" to "UTF-8", + "maven.compiler.source" to javaSourceLevel, + "maven.compiler.target" to javaTargetLevel)) + } } - test { - testLogging { - events("passed", "skipped", "failed") + } + repositories { + maven { + if (project.hasProperty("sonatypeURL")) { + url = uri(project.property("sonatypeURL") as String) + credentials { + username = project.property("sonatypeUsername") as String + password = project.property("sonatypePassword") as String } + } } + } +} + +signing { + if (project.hasProperty("releaseTag")) { + useGpgCmd() + sign(publishing.publications["mavenJava"]) + } } diff --git a/gradle.properties b/gradle.properties index 4f4aa52..98c65e7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,15 +1,41 @@ +# Project Configuration group = org.eclipse.keypop title = Keypop Calypso Certificate API description = API dedicated to the creation of Calypso Certificates -version = 0.1.0 +version = 0.1.0-SNAPSHOT -javaSourceLevel = 1.6 -javaTargetLevel = 1.6 +# Java Configuration +javaSourceLevel = 1.8 +javaTargetLevel = 1.8 -# UTF-8 required by javadoc for special characters (ex. copyright) with Java 11+. -org.gradle.jvmargs="-Dfile.encoding=UTF-8" +# UTF-8 required by javadoc for special characters (ex. copyright) with Java 11+ +org.gradle.jvmargs = "-Dfile.encoding=UTF-8" -javadoc.logo = -javadoc.copyright = Copyright \u00a9 Calypso Networks Association https://calypsonet.org/ +# Documentation Configuration +javadoc.logo = +javadoc.copyright = Copyright © Eclipse Foundation, Inc. All Rights Reserved. -sonatype.url = https://oss.sonatype.org \ No newline at end of file +# Project URLs +project.url = https://github.com/eclipse-keypop/keypop-calypso-crypto-asymmetric-java-api + +# Organization +organization.name = Eclipse Keypop +organization.url = https://keypop.org/ + +# License +license.name = MIT license +license.url = https://opensource.org/license/mit/ +license.distribution = repo + +# Developers +developer.name = Keypop Contributors +developer.email = keypop-dev@eclipse.org + +# Source Control Management +scm.connection = scm:git:git://github.com/eclipse-keypop/keypop-calypso-crypto-asymmetric-java-api.git +scm.developerConnection = scm:git:https://github.com/eclipse-keypop/keypop-calypso-crypto-asymmetric-java-api.git +scm.url = https://github.com/eclipse-keypop/keypop-calypso-crypto-asymmetric-java-api + +# Continuous Integration +ci.system = GitHub Actions +ci.url = https://github.com/eclipse-keypop/keypop-calypso-crypto-asymmetric-java-api/actions diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..afba109285af78dbd2a1d187e33ac4f87c76e392 100644 GIT binary patch delta 39359 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 delta 36880 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�@DDK4oKRRf>3Ss&^)&XXA34)HYI?;^^M=L@M15g>;Y2+EJtpwwL27$YoL4)-<}C?c{o-!iC|w}1M98{zianUmyb&rNVcQLq_#6@ z`DVPA){b8j3FUBTy;wC`k^b!OZvtu2`NL75j-!LqUUB_n@Rhkko49YpS zSZyzpG8b7*E~7$C+T15)w5yGGN)qto37^B^>2NrX7)u{%DOjw#`-1qSe1LjTJ#5#TJMh%ja;GT*+U$CSOI$fCW)7(IG zYDW1bMl|j&gKnFCVj1%AB|}k_fE}V~AOEj=3w^KQ9yAPz7GM26aVWxVMx}h!4Gh>~ zQ~#uefh?Nhq9>$f>?RvoCHB1aV$ROb#m0$&@a0Dpg?=CEW=5V6smm;A3I?WhEo1g~ zZO&VJZ7KYdlt&kS)>aaMuVvzj^M(bD#4vx{3Wtov+HP_t$cT;HEnRC8zD~6EM!+8l zGpI*)q5VZSuNxx%LjbK-es*>(8=x)CH2e56`ttMv;{>vRG06F8_cCz z!%WaTY%e=MLd9E!2vy9)^gL!jrkW12aq(AyAr!%5JwkDDS2zEKIIuRScxvs)mKAj5 z%9f?HV;82vo}ZZ2JQPl?DJ+f|1tuCVnXmx4$X~Pwbtw`@Sez<}q!wM}018CcdtWAr|8t%= z2F_pkfK0S{q2nW4BKMp;=Jm*Y&!*RUi|P|A9bf~__ejD3l${Ww%s$55AEjdNmbZ!9 zE95%VGI4H!FCC-$Kv$fBvH#FmGGB4s~`Km$Bwa>yq)8msf$B4 z>r{n0h@;2b*}DeYs33a#xLwAz@Yf6q6;XWy;J}A&LWkaL?OjyOa!@o?-)qOvau?on zBWJR`KqXctcE0SDB+Qz9Y+JHLo)fgsI*pEH`jy!4#2)@ixpT8ET>(4TSUCcO`e^LC ziPG|oByfQsXkUTWg4Df_g%<+k*%54Xf)M+Rfx&@kI%I!cXoXGoPKv*mkbgUTqIIXS ze~D5VRIF!Bng7`Mto$;w$@EN&L16@(5Gm^SJz0sT>ss&6YG^ zff6+SM@($@zK2V#8??fm&0Aed`o`^EpZtRj7bpr61aUE-169vqbu!@WVaY8hr!r$s z?8fW8Ku3Iy3T)J>RcfzQ`n+DJ+CJG=02ssxO%{OVVu)lP^zS%0t%?$NDpgMK)bKbiENDjZn4~f*?Yby*?nyN&%1$w}Z#Mam~NBrDiYzYKVqP8VJ zk^ws>^QjsJWos?{$LTV>GZUs;Q=t3+8Q^ev3*=W z*j;cmn@`|QZl1K8EWd1q$|c0ptY(~Q2~xZR^(bpSO9G#@T@?k*cirjDYQ+UnR{Va; znCg{t!WJ%a70d!Of>OlmxsTR<%MrK0hMKiB=y;*)xcTj6`_ViZ?xgQpBhE#R&1YL7 z1AiMxRBysU#9{zMi3lwjR{kkM9B^laU&2o&8lcXaUg$uQ=2$eoU^l_Mbo{LH zk=LP5Yr2V6Eo-hsO_xT?$W5Cc3j1K;EjEmv#?Ee7z7&2oePVmx#i*t;%-$7&ms2j? zB~ObzW%bfPDdC3|v8G4jMW?aIsVl#I*h|ow?!>Q!D_(e$d)N+s4C*EE;#Cnbjabg z&iIUnQ$2));~t(REUPV~xwuv7>+GhMXS1&oIw1SL&N*_DbrQ_|5e62Z-^|lbI@D+9 zms^!K+kQg$qtXdcq_O0Q{@bi=1}?Rnq!+mBbw%h+<2Ec5vokn)C@m)++En9(AC;-K z){dVmrZlXsE?l@NEV{l2NYos;0kl3-+3Oy=Y`KOOIOdk`+{!M?*P6F{d?Xw+My+o> zl|JI59|_X>Sqrkub0Ru2GWeCoFmyA@Ukb`8m==HCjj^vPEti`xm2w%@0Bf{W0}S_7 zRJ3Rc(mc{)s%(0{vgVV+Y34ty@=H#WEpICA-)XDj3Rg9H?K=1j#Lh zI8>l*_yXB+#MvX5ebHC=Y_K_iTm%)&yAovXU>-o!Ha}P!AFd^qSZm1(z>Hs*q)qnN z1p+TP9_lkecSgS)rauy0e*#26&e$bE^l&yrLy0c{O$VPir$ad!*s%<82NoRp1;7L} zhXtey#&N*5?kmkNS|ysmT)e{=OY%JDm(3TS2fdQNKJ#lBGl~d>8B)AHB7E9>;+Svn zxwH8rCZi{5Z2F1MPf9(rerONzT|Pa#sl(!J=;)1YUqF5WysM&fF0d}JlJbhB`(-fL zlr^gsY{NY1o*~14{gn$bu>LYdOmyz5+B1y8bMUK76Qjx3XWcm14d|!f@3Y1UkSQnf zO`>{aum-uH?JMYJ&Od>Wv+$e%G6KMAW%Rrh;=m4)5FEB`e);Y&g3Z>jwd435;@9iZ zdc4s%`fbk!hr-{8J}BIZivtdwvjAfQE6_)Nh^gUL%neE)m_F2?UDNeEi3J3Z%^w4F z(l{|kTdT2K+|Sch`9cEf=8augsP%aU=x!QX~=Y4_w-No|5p`I8o*+@`W((pNq9 z&fSDx|B3)HXyeM(a)wB5`C*NFKU%nPKPB`H|^P z9V1TtGME5L11dycLOk*NH+Lw}0NZiNFYj<=McOH#3C=5?aI0rgYJ8#crwXnT=Wsq};!VtgMlaZa#3oQGLEaa*QOEPA0-~ z8NK>#W8__OfLq2<`fLB;13e@AnK*;>p*yrw)$$1m<;I2)) zF_cZHq%(kr zNsaTocAvfl2a8er%kD9t2sAlBAcF>DM5vU~=1xYwU&M*mGXPIk+%*3_erSmCF0+-B zw)SN&q9tK6DFyN+K*mmKi5k1q=uX0wxFu?f>L&ilDeaZ}f4DAE><6 zidE7gv|DRIGhr%~P?}#cmLxDT&cwoCq|Sk-tVxeLoylFwu-(bMF2>uj2dv@UINq0c32YD-jdx3Qn0HJxq<2klN0l}` z=$~W%mYn3=1M?Ilbbq2`7P|GT8QKY6!O-Cl2=9(SnF;))j)FXf&2 z_|{iVb=t1RApRtMQ|7^`+RWhwpBTlqdQ{E2J@1Fgen4 zjsHgnU3%*sna?` zkZlH;C8tBoC_=p^k`5)-ZDvw3I_M~?LJx!FUNp(BEY6p9W`h+wp`9%U8jQduQ{3+D zJ#rHEfg&^P zxYf}SV;AgthK1}qN8#{oKS%f&`iSa~v5C8`R>cWCe&=M%yK(BEAOEQmXeSitQX_J8 zH5}xE&2Y8Sj z4m`r09?qMAvr7`z7)%Eq?z`@r!TyvSx_oIB5tEFdOQhNiQB7f}6v0TmXDM-Ui#)G* zE8Zwj-D(d_FrXT#+lr>?zQ?Np6x3l9jNNMT<+D92_Y0F?!kN+2&K)CvU9xwH6qL~Jj&%%+8O!_-uX2EcqH(TepOCn1PyCtg>G$-`;hb&T2=aR+V`WkJ+T7| z4_3xXr>dDb6SWubqj}wfeHLIl_h>*=zA;o%})4#qd(`s1zvY#>Hn()KW>+fcGc!>GIqS6KY&Mo1d&`Hxcohuvvt1M7qCqB*mY;x=d zpR-z()0o@i_d+QP+*1||0d<+G&Gt-xf3AF5=~2I$s?jCZYhDxKNfpa((_)OE$?cp+ z6-CPNNlb9GRl$xSR$F}Vy4DE0Qh9`T9}JQGMnVqBzYK_bl1OMzQDZDCHRVx&*Pi~d zpVwY*_>%zO$>#nkhYRQt-&e~1pmpET>w>Mu$V2~^Ta$YP`PFSj{}=uM+PhwqAU-)3 zSB!6^I);swlrp9cFW9~!;%Seh$|4th*(TPr#eop=bDEK)g^^BSb;Uf^f)8tEw#W;2 zmt=B<%wv$simO#&7r!>++Zv>Q9FDwGJ@#O%B}HYTE=zQlc@{8j@CH6uq<+Tc73zCh zidjqXj6;hNZ}7{Kqmnb;RK%#!6+PJOpj_A*n=8T8#Arb%RxiBC8a)Q4l|1CrrKN*x zpef%Fae3jFx*3h73C@`r&zdnLZ&UJKZ(6+e?D?!Nb|->W6QPnA+n+JXT&q|{W4QXl zRhQkwYeM{?JVoG*7@?g)1RCq3TYZyL-Zn=s3P0FXC?`4vzh0)SbLn`(b2(9{RxJ?Bcsa(^+^hViRvW}K)8^vE=u&5}D11d2|C`4)A4Pp*S3)}S#eUOi` zL!DILb(&9swnW*6z3AfDUtzmGr$x;=DS>OES2YQLCrhr)U3wapg8830ukXFT{cnF2 z=DyF1B0y`0gw9OB=^c}IdK2+-a?rWrV}Y2Z6!=LfLy@5+tmdTv*I22JhScZcoJr=#F4W`Lj)7}VwU=G{lKf93)=bQJ4kK?@2 z8hX3%-;NmM-!=35>+n^2`|w$MLvj|8{~VusO?hMOF_fi^pslbap2jzYMr$(z{IFsp=X z_6dIdRW?oPIUJ_A>9}NLphLQ{0;O#jW(qE~<`JIaY|shr@32a|w|Gg2#T0`YVfB3J zGhGeck)CQreC^10UxZR|h(NDunGA`c3i90Kdk`Hy3m@4bo1kg8Id`&dxBEs+i`^%m z=gul#o17JgJefo*5DO`Ghwbc3aox$N+WJrd*Mzk8lI-(Ref#XpSmBo4SX|BM!j&o> zF-`fQ3Jm^87RYUvlwgI8T4Ok_`QbR&cGH|-%~qO?Ao$5CtukZ$9w00o|zKEFFt4fX$$LK0@7$G1H%u}DD9pD(rBCqa({q} zg1`7c&s%-4gN5dh54Jz%4hAASx%!s+T`)jc@)Sh4yGG{_iQ-FoFd^%j>b)wofaaYi zpy+@|SsJv8s;~?KD z#kv&VF6R+G0c5dduP(bN)|JuH@SD?8K4dvl&T6sE(Qz2Iw}s>RVCUJ>n9<`+F9XDw zWkuZNo6$&>)a+Djdj3E*#VDRFe6VvQS-se<^#bOy;YgY)LlH$)ldXOo0$gZHl>KUG?wmJLmJ-MJYT*8kA2h&0jUiXUL5(BV;zc}z09DctEp01_(DcXA=Zt=N z!BLMd&ccLX^1+LtbOu-rLtrM5iW{s}Eo+N@W8j2F1=ti&wLS_;|DfIfE;*T76y$Xh=|eCw!~f@xXG&^3?-kHObd7QF(^F!?r>U1+jH18l&d`-1`d8Eh;IrOH0F3Lqpu<=d){zot?qj{HcEUveNJ;3K>Iu z)}(~C>?a&k0k(Kb2kq&?Nf*#j&-CVpz%;tPDkeS$M&-=$_Gkvuu$l_!p`cs>!AB_p z$WWy7aPulVhPWEa#_*%2_W8VA-WZQ>{%%uzE_u<&5K@xlXO=rJ77wm^mwyo|A*-Qx z9HA9$7ez>a{n_!zd&gn+$wD8GL+|)tcBx?YD`0jLQ2U9BwowzQ5`s zDA>Q@pnl?@RYb@*gm;;MTU{vrB*Ur#pRlga(SKtOQ*d+7pY_@7J(_y;P*pn{ZzA%M8p zPfm|k4>lWl6k8^iQi6$D;oea@yt(mv!m;Lu%&Buhym)a$j_FHfjIBjZFQu^p9XLb= z?s8(Gt*A^raW7m>#~bac&ygrBGmaag^77tKXUJ(AY|`pCvhOx+qs@e`#a|8wwd1m# zzk%65U(-s8R_hGtv%PKQuP`jr@ac>m^wW6L+wtf?4D+vD>b`H+|MPdqYGcMw{d@|c z!4{IT7KzY_Gt4S1HOvrqb+H}yFHqgQ_GV@D+S5VF+vy{}MxVp}k7noj1b=Vk`kefA zz;-*0$;L$fopa{Z1C`>gjetewT?o8&wWy_D1fm9hdGO&o#vu<>{DxJerQ030Dj$O* zk<*jqM(Usj6h7EsmSgVkbb z!=_b0Z=!h@yq%PU((x!gN=2W|gw$e4cxeRv&y}{7c6Tuu8d@jAuvobJ`gTqqnMnsW z<-Y`#9Q8r?J{1_B2hs^?DE4V-K6XlKDz8E`*-c*dY|t^K5igw|yytmY4n-EmyG9uO ziY}bmA%sz8^OO*10B0bx2QVJFGiu_O*bt+I3hsF2mvq1fx^BO7^hE|Dr5MFPc~KJ% z`3Ak@X0GTQ1grcJVxcAK+=@IWUJ;SNN8^ke?rER;4_)j2LYvY;F_k;X*0D#^xE(FT zehCmh>4YtvaFAUC!CP}8Ch2xV#%TUnAQ#(`B4WfX^EWXi+c%PZqm(yj4EV@mP1K#2 zXD{7P{Dj3(Dk_r)XOGqlBO27-Tn(0p)-C@}bCgidtN#y*C!S!=CFmd^j0_+k#Qz70 z2*5fW7(eyZRUxXDUK4Nns8D8mL}_9=6Dcs$6l8L2FbaBvl`FZ&0 ziRTlWvX*9GAMFLzvYivp?{|m^4q8gHDqLI=%kTY)j0#<30_8&7u`+D*2}>{aQA&Hd zRDCd~^wN46mb_gOhwxz^Q4MiRc?~HGrpt6lW~Uz9W9Jd?Y2#`vygd^4rtM0fa)BTX zY3E%c0Z{7@otlzY;-4O(+C-(KhC~NXCtV`P;ly<(akvFUr5f6Zd2vhj$>lQd_UTo6 zU|))to7FBYW7}Z80t0-dW>$%|9|lXU5SM_xvIobweTD`sh)cf!r9{rhFkB<_(%B(@ zGCWYnv;D z%dyAVKS3`wtPiNI$FUkAdwj9O-<`ApH3wtpY z=fGe7$GW&>xi!<0ow9bWT5;MsAzMQreR0=8S0m0AnGO6lD&L#kq?K*flgwT_gD^v* z8=7wMR8$q=A^JCvb|9hk|!d*cvOo}45%`;t_tv%F?WnUWLRS!7=0daMi_f-ycf zLW`2RX_LRB0l@X`>87N_7<7kB>Sb7SWl?fS9$Bi;Q@aRuv=UCvjcQGcnW@&cWHZB8(iU^YCQY|);#Qi*~fXC0$#j^IK_14n_ zRaYmH;8;a z)?~RVy=Uf;(_h9lK#tW#lE#^BgXxn?R^LUUASb?+U;qomBz=U)lD(V`N>76=+5(Jq zeT;jy3|=ok0a(b;O*O*9pVYWCc{0kywJoSSy!JtM#2W&b%<{OTYE2o^c*pj*c7dE& z$1t|IhQ;?1Tz{S81Qvz9l~z&$|1Qj(Y-tdk^Moaz+NFALpvFFyb{r6gE7Uhxx#>hgC2ozwJ?Wl4*lM-NSgqs(eHP zDjm(7fg!7AQwnYT#F}aR@&QE2tRjvIWSYyQ_>?W1hHxyGZ_g~}w!IcvP-TcrC9dbV zTK$!v2%Iz(@Rm@fdXY@KlxGQt>n1g96e}-u9or6sT4uTa4)WEWozJiZ?s0&$A2gqr@aokSA|^Y{?bu`be`o5la9ou zixVS?H@7ir3C9e^d;}i_F{eePhxZ!DQ9=?=QK!xYjLny09RyW1AjM=miWDLchoK|z zz%_zPkxb?zbFnvDSXEQ)hO?zeRtN`i((l3HpxD)Ad<88^m#?~oR6U|wScCMSM;j0{ z>(I-;2l<2hjmxMw`X*WUkX7%JehpfH(^1!7iv~-Y-+)1N?y+Q_a@eK{^w!8nY%my3nNOd*;}4sx$gz3y$v|vq zmethp-;$m{sCcl^jKRul@6IH!*TmV2ijs%ySDtY9i6vaW^m_KqBP@jS@X0bC`t@>zd1DuB9X?;cVP*?sdmjc(7gh;zxdqBhqT@{((bsug#=C4C6eQ!5Y!imRs@aR{bU zQvaFz6bv072{H}%>w(j{bK~^0@R@v_&#-IA8K0S5HL-l8ZzN%hmd^-wZxI;LM0-+xJ@tA+r#*4;&I@^?w2X>yi3Qw+}kGq^l*fg$33u&t zFv)^B{kh3-V}}NVM`G;mBucVokC)Csm`Q$WSdtW7Lfy!Tdy#b;K*xqpq2r%kI-goH zp=5yd8vt{KGU-CW*q#~#fBrKxsmM_`Db8+@gL+p|elixbd2RmX;%d{j*A+bl1uos<-{FyH21CH7jcSUPAYeNE<3Ck>6bq88y z17+pv=bT+Vtv9t#7@d9HUZpB?gH~yCI4FG**AOOwSsbo5`s)kGL6&!C@o2`~BD
      H1s<54&E_H#isXY9bXwNdFFO;lJK4?{sD>)OVB6l9{ z0zTr|CFtY`^KGZ!>^5lGlL{7Z3*x;eTE1d?0koB`lgF{PLZ4KjUm@&eW%MUVAGA|+ zP?e1Z<$}@@Uh&B-=)CDcZjk16r8up~$xN!Qj?S9XQ(ftfg-Rl%M0LgA&o<65ORT_d zl-CzyM;!lf$8bxw!gH%jx58{WwoA<3k4w#&SJN|x%#h>G5~TgOeh|r5EO|cJB83q4 zzB0c+OLl;!_Y>7}2iR)AsMB5@dkgEybww6kZ`ht*_>V5iBLKGOq&7tCclAxhCX`IW z>$Kd&KVb=>Je^yDQs-%q*{WB7<~tDXo8+!(+eSqYA*?s-r;LbO)~%*3aZSkz;8gNJ zvVS7+&F*A1L1i$?*Kq$dSVU#s_2DHtA>+=O2M0P|hWqkN-Evzf+Df2YgNBYGd~Vw5 zjg%*&qASdT%^DBDtSch7)KO13|0Sz}_OhUORi=4W;t0~3um>mHPtW5eJJpLwzuUll)RWur`>~sXKas0ikVP+qK6)-AkpxbOH6?2C& zcPxIoz&kh^y|TN^ai@BR(auQXglG?ld$mp0eMtwEd zbgmBX#irt}QLW7#u$Abc)qWA(P1Hw(g-s;he)M5nNGq(07K4q=3#e|$cs8YVU#n^^z&;EuHmrZ!CLZDll?r;EN7zR~x+i1p zED6nzNi){-&-&m$WcUCm^zUB^;DK1+Lp8*@40~K4p*ZJ^4-U%1p6i*J`G)F6}1 z(T?(b{DE0VXT7y)}&<}(u#+_Z{RE<)F)*eDJ5jEp@c;c z&KU;XSYzGevlmEyG~kS-AmK|?D**j>HzDr{o0<>;;x|xXHBg7axDAcD8(Mo4+(}yEw8+PEE#qviS(2D208?}1@6DxC%5%NP*yC%^h z?18hUFROQHqwL$X_wlUuiR0&nU|mpDP~s%S{}*1MX5dE|6CA0kQBnjB{#!Jt54qBZ z5$@Fo76KUF=tz8Q_O)uGUbFaw>#&lje8D{{SpYJaBPaeJt;zYQLI+O zsLw=CfQtI$Q=*!=OmsQKd5wV%W7t-{cy64SYUH% z+o@1)XCaU4vO#G&Lbav))IyP9XcJFP*_DeC~FU1c;UE3dhi)IK z*bk@+)u^ic5ca{S`uxP#fx#lB$>P2x9A&2>kjJJB#(_B}5#AX47x*D6Rrx=|jIB&| z1C)Vr7fjgScnanJMCtxa`taHPcl5UYBpFn-zmE1$&ei|6K3)W97LPt^QhT)k6xWnI`TnvQMTwr$(CZEM9& zIyO4C?T&5Rw$tIx-se33Ip4mRcjK8?wPw|-x854fxY-ks9cr++HEXb%p?|B+AaX}#r{*nHx7)HPG}LO{D;|!g zY(OsT4JtFY`eo9Z@bP&gD*P9$Nfi0o8L|(Y0v0n2)7k}?&pjeq1(<5nHr6v9*0czO zI-=jD=QRV=!}oDAcCIr{)Q3j-6cB*j>I19%3nm%b>XZ}{2LxD+W&h?uSTf;NqQGE0 zB-S^Dbu4ULIx`7(iyU0TpoJWu8e(--{;`?sD!1^zMzLdFmrfa%0?lLkf)g3KIr5tR zYLo3V?#PJ(l_@)O(7qqH-7o|<`3r|AyG~Xt4uNi@bl`^O*(SW3)7-n3mK`k{DznzjG`;3|D z@+tRe`I+5EHO$t>sGOR>X}zNw0^mMKiD&lbs=2?-rYUz~I3v|)7-BTHCeto8EzTL` zG`@u|lq@qI)u9uj+c?r(3hvl%Rs{8k;>Z#Pqu^~{FMWf9Q?U^Un}ze&BQ;Pt&Ys(R zd(mlJl&JlK6u8hW(Y|d4kMgC9=zcI^yXdC#b)o{EtEtA#Y%TynIryc4HQ2l<|&c z?MuGrJJi-_ew8XFaix?y9fw3O1L42Zk){(@PmZ5fjfAAy0d{~4wimB{!73EHyJEJW zsZ>iQkX%-J@+x!W-v&L0#h+aKylAdyCQ~D+yOoU9g78afjn$d3Mn^Py5o&AEMC+Fv zUGFvzOR6mq#_OM#YoCsrO_wx}lh5}njvZh}=>Xt{nh-6TosJH=xm45Qm; zaE4qYBkB0MX&wN&j5{X$%nDgVTi-?&uWjUOTS%~{dOyXj6jYvZs1h&6a48w@?-|L_ zy++zC)H|+R0R|&ThTGeM2!@DPV@!iYM^pp&1DdK2HT<7&kAcM*4zWJ;K^Cn)O^ieM z9Uj!q&`bJYHDeFKFv9CC9TEc!C!@(>in4a2t;z&fi#gyF_%NoX@>o@>o^}b@@wLDI z-{a)t`u+Z6|N2n-<>UJPys-ImP{8P<+EGU$HI+A0vm}uBT~$C0l}M|tSgS^IVj=Wt zu0gtwtG9Kze?_Myb!cLTZTYcS;UqSGjCm3MoHVsi2sx`>;|!GL@7NkhNLef}<%^6t zSGQ%V6IFnV)L4pY%gmt$s>(rgFIL?On=v1=N%~N-1-09NDm%L>3tR2##mQJDvsI?) zsi1AGRm-x9>Q(ohW^{AE_h#1E295R{6WTH#&s&Z9?_QE!+{O=k)L(Q)+SsUw(^RS4 zm5zi1U_noV@(YZka0@z-9}v~PXNfIDhV;+Vm)!sa0Uc9)*U5RYA#I9C<5Q`Z=DPWN zM-8oJ+Tya3<^tSHtBDB7*7tx;zGAPv()r9V2`lg9IN(qK!;cPh*Ou>9qP zq{d#sYRPY9uzHQp)Vhdw7vCAYhhLi`c18iObp90kFU-e`3K;xZ{)&SHrXNUXShaf! z%wI7pV2|yW;~^QDi&0ox>d~@SaO&xTDOO9MxVB2afWdJ(VdCs<%-mM;L{!35s72id z;%Zd-#M%4?>I?P9VpyK-oU=lDcjd4)UMpjI>o+!R>EG<_VNUI?bC|dL@hVVyspbJW zZe<8@7_2ITJ7yojC~ScM_}xaaWF^`sSujtADj?aeHnj}P4ZB$>YXIVz64#1~`WM&FPvrx1a~vLiT$}SLx&ZaAPSLB$ zgHr}x_?IT$-p+-cEn;b_DCaT7hz@`ymByl(3W{UV)0Bg#>~=T#=J!H-S52Z*Z@gE5 zuk$z1)y7-@ra;#jJ?CbsUkMClSuPrw4PYMky#vn9JaR9Jv0OAj0r97wP#HElEwHLtJoQw~Q z??l5=1ID1&!k?Hmop(g7e2!^KzU=^a*xg`GxL*VXYB>8%U$-@GiT;Z$Qt%7-&5GDF zbLake)U3w3>pzgmd1R(K&jHk?1p4qpQg3L(t)sCK>$ zODt*v`=NN94@4jyR1iN%KCC-K@11>-yH&KROZc1nmiE&lQ_*{uP(&HPlS8-Add@0l z9`#622nh-$F$^n3G+%U+P5=|F{wq^qFw*ed?oq?3ENq|=&VcvL1piZ71zI6-PKCa^ z4ML^VqraHA4H?DE+rwzNcxBjtI`40#Yn(!y#C1n(AT1Vdwp?O!XDd?^HlgwW8}1pO z{t#cvC3{?rxHzT8$aNcFRD$YEsoy5#hBr3pl(f}{YL6uO-o!NLADNJyp5m;C%RBw1DD zD-pF#4W}Czm#DT}KJmybA{s5%gdK!VvWwhmor?jol}t(Ov^!9M*8SBNgx(|X0Yrqu zix>A;dN67!${K0u@wUr=DX*3kJVb5{@p7Gi1TVN37O8;QkJjvpL3Ae333dYy)*M(c zC5iB-v}YP~?M|2JoG1qA2OuFOm~cO?-ko)cGUB$m5+m0VJHhO|URYL=5=(h3O|+6Q zyWZeMMk3V@{7Wwe7ZfidiRrgK zAHUIBC8IrO=~UkS&c}KK;eCTN?vCSw!By~;Bh>xq9)TZD{eQv+v9}OOT(>BI*d#jz z6d}YBi~Ps+18JE&P+wp)D}=owXlP<6Ni|tC5isPPc)IK($@av~N^*Z{Ia2bTTaefM z7#0thWV5Vva+XGxhrG|L>!+C=gWg|%fG4V8#j6OwajFf(^7AtIGRxKz+w}%)gOWmYr(9(mTPxK| z_emd7u&_sk|0vA^%+oXz$W^5avRnb>c`O4@4N4W`DMN|}=AeEPLS1eRSIuXQ%Vu3S zLf0Jhi~Y^RGW-j!cC;xir>igck@kj-w%l)mCfkeX5e0eMnf2I@=$3cDn3X%PJZ(gl z{d(V9agv3!*}qFG1AAw?s~5g zJB>w!=Lc&pr~WU*6`})TVzlR1ajg!$uBX=$Y8bpSrA&VyS1@B}@I#NQ-g^9EW={5& zde^n}YSms^E|#jhSF#)cmEfj^>3!+K9eZ!T9GWqUrkNj8{(+kstp@OB0A~@doz z6B;Rbuq?IEbw3}Pq%1g9;qc`zVFVEz1!^HyEK_kPX9Ku`F+!KJ@<*UC=y)o!(Lb=v zwV+hxLf~fcVvDp-ia=vf@eRnNM8UioLFv}DL8ZnC)yPIc*d&#~ z*951s@Cm_wF{478Vqerf4;Ne_5p2HiD>{OUB||aG9zUWfwJbZr-(s#x67d+I2P~9o-h2 zBOhhm^``sX;&9>FYn!JiiIehE=*Bzu<^A%6ulxQ6!vMSv>^t!;B1S^QQ&tFVoBF^G zI*7AWuz%yWsl! zU>dhii2BL1b-g@XoU>a@tgBl)9L%h3a)Ul@G_7uxGVD3S6FmB+qq42Bs@Jj z{U7oyS*^`gev%WYu28XNKzQ$odiWvE#>P9lLI_S?SWI4yJEN&~8xn~k-N>nUXYw(m z5bqEj0BR(@WN1z@iwv%<1~@(jqw+%UK6d7-$bGKz-amtGe@ElLU~uIJY)|-{7K-0qYPq43uY)%cQ$xYVbBNa#n2x2Q(`!vu&D`G))psF?-(NH*D4D-cz0J z0h3e1=HxXsFGlLqoorXGnV7=))P_8vX}@*a4-i2i>s7vximjX_=eqDA)=|JZ`%dI! zw6R*8aZSy?;CM+_%>f6)YK4V}&B4V5RN1#8z`42z-kbMHrwFSRP>O-wGarwN%B;)- z1LkoXm|clXtj|Iy=!C^nk+oLHqut}@w$^m}+~YhZUc+M2OuJ;S30CZCy^C;ilEL(a zdEdTGBdcH#;(zj;5)SnA0!8-pEwaOD%SM^(XQfBpY%Ma4wp++f(3Z=bS~@2r!O4uc zl2CH3D3nHHWe8t%hA}wN~TOH&*erJ(nB# zg!#90_0i2!U7j1rvj_&&(Rl(|#r)E8$~Z~LTvJIb4XRqodbx>fwtFvQy0uDDF?62h zwxR-J_6A6(sqaqrQD}#H=?L{(o(*=7&ich`RoMM3()HSojAuIhFjZfio%~@vK!ewx z_~uxtZcS@sZM_^~vfEKiDip0=_Fn!(cB?G5s|f$h3Ja1HIjBlNGEYj>&At!q6*gNQ zPZd5|?ZAZ^?q3@cT&Py>hd<32N)3$V`xNlQ04p>BGxjGUlC&bq)3}g4rl%7#P>5Fy*;g`@bKed! zuet?V)A@dx%>v?5UtH^qr2c%IHI-gvrx?3WHjr=dUbQClfYP1DhurXv+6TyYa$W5VASOcecQ*57 zhiL@&xi8KLehjUC#O+HY4%Iy;Kq%R zrm9vI^73nsgV|P)x4B#L2zEk~SAB6uP5z0ia^t5@Xlo~Tg+-FBEK)BWzpd!{=GjW09Q&&qK)lx?^>KWozXN^e zcNDZpedZU@WFbJf+48UcrbOq;Yz=glW$#R*f%rT8j6c{F58u05)6UHr$3o9Uqd_!d ztBrPeyGeA0H|NxYlskfVWaDr5oU(khSw;vePk6O~ddDYPU>8XEnF4e4`=)@4(m5mD zaM0Ka7bpkB*=9^i0C1cLdeibMtniHSKV0kZ0qI?`Nt~KEvRc8Mp^r*hT6OwD?NLoZ z7DSR5&dk~4gr)P$1k^5N6uC!3ZsFqhGvNUF5|c(v!M`w2o^xijan1VToantV9h_D! z_zJ^1wT4oc$V%o)0>Q<`So*^Yu&FZzM&Pm2YtScEQrXq6fN6Cu3y*|wViwISTy~yt z8&H#ImzHHN9g68?13{BK#{uvWI45_J4O(LjhQu!<-cSofH@w+EF;;tGo8{T>p(#z_ z?+lz%1T)YL%94#pXM`a?i0ct~Sz`?RA^VQrG{om!-s>S4)b9{Sqkv}_C&=(nnbNai zMnd&VNvSGMfJjA%Oj^OY0I0HDUy>5pLa%yeq3d`pZ|sjGVyv{ZCpsDO87-_{;c}p~ zpwG7lw{vYsaJB%yTDVh|93W=S327tvfSQxNM~M?1c7B51r{@jeNYt;AgPgClOdY%C zn4=dgB^mUqDjNgYhb0ExyDh=8?SUsl-)4=b3qNrLywqvh=M1%sPo9&Zy(vmSCo|hL zR|aKix#4F(15VqR^T>&H8l2%b!pA^f9*1=->Vo}w7zED_+T}o-QcagIj%o@P4^^v+ z6=?~jj1tupTu7$m4t1AFh+TEg%sFd08DpA23njfoRA-3xUq>ZfA|5jfI!qo586aBW zTW2Q$%c0q-n`&AkBfR*K?fCJGzXrN@uD22HKgTByawq!sj}AEZ-l#>u!ML8Q;~uv< zf$i?V$7A>~q9x@8<>P;|Xv(%vM(E_|4C%t5*eOdwk-(^!%3^ZyN_wCvZxDxrMZrfP z=kWLFf;_jaVMh%W1EJX#!*MB~heaWaP)5)L#w98Aia@)g^nzn3<`hi^}PK7V*lyhj)Gz4s0LUn#X{77au2Q#pzE z^N;`~;p~tlO~IhR9#T_J(Tq*XH6H;3#bY<3!_FXqlA@XWB%R?Y0;u@v=oA$ZZz9n0 z>6*Zh>H>sQqEVrRq;7Xw^e%+g$Tq}$!Sebw{)z@m8-JAEWyCm5+Xou!v|u8CXHQIK zJM?hzu=x0Yechw|B~&M6C7G8LQ5A+EMmENLG=_ZP4CA_nnJNQ~V~T^oJwWbCPbSrK3ZW_rcJL;f97JYA0~%;Zc0KyUjzHQC)<7vr95#~ zS!~T}XS)UIS7O@aaY2`zA9UOZY}QF4*mV7Hm8?^DWE2s3m{6D?-3exqn%~GMB};{5 zUL14QFDDKDCcs`0f`5}U@0}5NBd!boUIjjh*$6;YLKjU0Lqi7G)E( zN=(*J7#vD{i?@XO$wr=@BdDrftRrM21xppS2dJpmHg4dPzZws7W-hUg>TAS+JL+*k z&&dRBK|0FF9-|BuHe{97;#L(gdR#N>Fsb+lsg_b!2S5R)KFKy_eoS_cQS}xXOAoh@ z5yPEWY?TjrZc(n~3U{Wp<8DU35tj3-Rc|7ge(@m&D1gwjw|%=Y?KS=>i1t6Fd%Upl z@3quustA^GWtde+aCd2{EnLHZ^$VfFguW@f=DX6fEUni%U8Ox=!|k%g$0NNU#9KQ+ zt^rz(0=&VGVb&yZ;5^}V6K=99fC~J5g1Wx7SnRxo%Ps!8vE%_VKu9Pd8hxH?^8m8? z1loR5OmFG=5)oU-6;q$JL@~5Qs7HzVc*94k4eqf7bYRe%8bs}eq>(-rq z%)e%trDJw1e2v^v&cVl7_D~*RA-#d}gN(p;JyAsPcdJoIZgCTY18E!sqKJn@Z;43JzFV%(BgYYHt1Bc(u8>Re?n z(v3z{yS#Ivt;dkq2NcJW*ec1rmYS=hE;JR|ZC96y{Q!AjVT*VYoL_oj3H6>Ycg!Nr zW0lCfrLZ&`PjWi^2U+x5^2uG$9uvCU(g?9qfC(!`)0^%`yMKqVH=e@f6T+*`V@I)J zausfj&ZOq?A-HkZrBx4ZU;YdFPwkIc;$dI)^E6+nI`kN9HcM%7WpptPjHGQ<<%jF& zSl+xyLV(kBvs0V#sj{Y16*wHcQ9R=zjJaX;lbg_g7QFTQkpfGmU*m@LDt32w1qrUY zCYt>Hcq1kjLHx7sB};;O9KOkPu)`ItbTrQ*5>G>dSu@EMjM1kozvOF(b9P^p20n{N9&b{egHG{qi)F=T(N}U6RMQntZH6Dad?wN zkrk_jYzxE`t-p|Qnkrm3>oc(sPhMek^?)JHXoB6Jvp8OP;J~7~82{2)0^t)&b$#u9 z6F_tdG0tiM2b=; zQuC5M0Ww71OjT=&i~AZylYSNX>Z|DUlaWQ%O{3yT*&3k+@Ln%t5LBNL#j}Vr|z{3vz z#cNEo9Lh&hAgMIKc|#TPE3W#mtzn+KL<)y+7ra!;H`2u>Yt>37Q9lv~V-q~vISFm4 ztiCvocA*FO_g|f4taMc7*{|R|JWo{2XGweG75PKPoV}U$%eBV$t5Uwo&xbq%e-Os- zaAtXvJ5V?o=CHcagd`#(Epha;fwrJZT!N@6=#dJ=PI8f-;6@w{(dJ7T@Tt9405W{T`wJzQhrMY8Nak()X>%%BQAq(!&YLI;cX zH_^n_TX;(y%MEjHw=M_Iy3h*Yg%PE-!!yW!$mSM+i-g~$ziu4yz%Mc+n3++Ge4u|XXWt2u4^IX15$v&rAKr8Y@5s& zHWyNW9$9yKT0ni|-P<6m$RVK?%#lRLv7>LbpqQ5DA}0O6uqJ;=!GSG=5It&ijBg#M zQ?U3G=c7pLi>m;5o^+lsn^KA6yc!AexYEJ8Z56TeC#A89lCvl89~FeZ;)`(OHU6;V z7-Jn46-Wtd2V7x|wq?Y*1HCYjtl^;}1_2331r55>N3?Z9%-dsk@4Xlv$D!n_2J6(2 zC#faLca|^LQi)70rspKUom%ui!#{V{czqwpYkKYt0JMMimdW6aP#Z7u{B(B@5hE#e zS)qvvE;=)I$#v>9AJmASavDCK8$m}j$GD{*3pWReE7 z?A4T|CdTZ6WIJeo!r=O`R{F$CkD+de?TGJ7faVqoJh6BJF(8dglg!_wKMhj!NVp<6 zQ%P+&20f-&_+V0sDMWD|_iO1;`w>U0^_Us0(hu0VbC;hn9RV9{ag6aOsP(`E1lr==LR@Txi_VIze2?z0< z!lfjHz!q8g=#m-*8J(N;AlGl9M(iit@}AW1aa_%REp$h4ALxCIn*(63SK3e4+d^Kf z^u;223{(4I2<0EzNZ$@=6BprUAXO3&Mf1AA=fEtSAPVxP_f4n*7%ab(6Tfl6{`+BQ zqOR1I`>~#HelAK7PclNoONu`MPx3z?2NbL5{aDYad>-%himl2M;Zej9YL-Iio=hbn zWpc8ai@%FPUh?tIciqz_?IQt0-+!YS(q9dIp9!$;4yLccgT-w2MNXzSxn4GXgj z6x(|e!;)d}T7h+(JU5+s&tG|to7oz?>McAK&*m?tASJs6ZAB{Z4$nJZ2;xGmp!mRg#5vgWAk5-rKcI`^2n}P$cM@z>gv#_jN z&7*Kp9mo2xbs;Vsp^A6Pab=Ci!QbCb0pDvbmyv}1i(*lb(S=*YvL!dB01C_J4?&rQ zJVw91EG7oi4_G-(OtBgJ)sIjoG=Phi<}d|6m#;Gy@`&ygUzJB!z2YI8L*gS?n%4~} zknEqygqTpPdUMxQ6ECwT(2T40*3l`vEWhXU#*FlGTf|2+FK)5Gp1ynnCL6kPS*HIeJotv0k`7}TRC#>qy2f4YXHgBcfbuZZgg{s!f5sV4d}mv-Uv$aD``WB zW97tf-0H<&9jk#EppNV|@ITD_f<-&{x}h0!fTtUI(z<$0?U6Ag2f`5h3Ho2)`Cckd z&;D#zk|O~D@%<0*8fdryRZU{m=rr`OWAX4sENuKw#eWLE;;aK0|K?g?o94_80noVj2kO2U67O^(V=`C5QZE3`qWHiqsfo zbl2FWOF_*bnO=cH`nhM8*n1SQj?Cc&rqWNyw~*S=CCuyWQxz5uVB;=Mil!Nzb6B+$ zZbVbe8&DxWlR?q~xZ%nbL{`!6aO;2~DTtW@<0Z#&HC)`FW>A`Ig&9CVGovrKf90v+ zEh}5KG&I;WvR5@1>5(kWn{m=_rMzHEskkh#GwJglc^7}@%ukxi7<%9|>R6?RZICx=F1t88Hsi?5&u(9{&av2N_%0AV^qz&fiv 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 442d913..c7d437b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0..65dcd68 100644 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 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" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,105 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 107acd3..93e3f59 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/scripts/check_version.sh b/scripts/check_version.sh deleted file mode 100644 index 4417fc0..0000000 --- a/scripts/check_version.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -version=$1 -echo "Input version : '$version'" - -echo "Fetch tags..." -git fetch --tags - -if [ $(git tag -l "$version") ]; then - echo "ERROR: version '$version' has already been released" - exit 1 -fi diff --git a/scripts/prepare_javadoc.sh b/scripts/prepare_javadoc.sh deleted file mode 100644 index 12f479b..0000000 --- a/scripts/prepare_javadoc.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/sh - -echo "Compute the current API version..." - -repository_name=$1 -version=$2 -is_snapshot=$3 - -if [ "$is_snapshot" = true ] -then - version="$version-SNAPSHOT" -fi - -echo "Computed current API version: $version" - -echo "Clone $repository_name..." -git clone https://github.com/eclipse-keypop/$repository_name.git - -cd $repository_name - -echo "Checkout gh-pages branch..." -git checkout -f gh-pages - -echo "Delete existing SNAPSHOT directory..." -rm -rf *-SNAPSHOT - -echo "Delete existing RC directories in case of final release..." -rm -rf $version-rc* - -echo "Create target directory $version..." -mkdir $version - -echo "Copy javadoc files..." -cp -rf ../build/docs/javadoc/* $version/ - -echo "Update versions list..." -echo "| Version | Documents |" > list_versions.md -echo "|:---:|---|" >> list_versions.md -for directory in `ls -rd [0-9]*/ | cut -f1 -d'/'` -do - echo "| $directory | [API documentation]($directory) |" >> list_versions.md -done - -echo "Computed all versions:" -cat list_versions.md - -cd .. - -echo "Local docs update finished." - - - diff --git a/settings.gradle.kts b/settings.gradle.kts index 631416c..7f1d415 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1,16 @@ -rootProject.name = "keypop-calypso-certificate-java-api" \ No newline at end of file +rootProject.name = "keypop-calypso-certificate-java-api" + +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + mavenLocal() + mavenCentral() + } +} diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCaCertificateV1Generator.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCaCertificateV1Generator.java index 15afd2f..8adba0f 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCaCertificateV1Generator.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCaCertificateV1Generator.java @@ -1,12 +1,14 @@ -/* ****************************************************************************** +/* ************************************************************************************** * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * MIT License which is available at https://opensource.org/licenses/MIT * * SPDX-License-Identifier: MIT - ****************************************************************************** */ + ************************************************************************************** */ package org.eclipse.keypop.calypso.certificate; import java.security.interfaces.RSAPublicKey; diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCardCertificateV1Generator.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCardCertificateV1Generator.java index e194ddc..d999933 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCardCertificateV1Generator.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCardCertificateV1Generator.java @@ -1,12 +1,14 @@ -/* ****************************************************************************** +/* ************************************************************************************** * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * MIT License which is available at https://opensource.org/licenses/MIT * * SPDX-License-Identifier: MIT - ****************************************************************************** */ + ************************************************************************************** */ package org.eclipse.keypop.calypso.certificate; /** diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java index a591519..b41c779 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiFactory.java @@ -1,12 +1,14 @@ -/* ****************************************************************************** +/* ************************************************************************************** * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * MIT License which is available at https://opensource.org/licenses/MIT * * SPDX-License-Identifier: MIT - ****************************************************************************** */ + ************************************************************************************** */ package org.eclipse.keypop.calypso.certificate; import org.eclipse.keypop.calypso.certificate.spi.CalypsoCertificateSignerSpi; diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiProperties.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiProperties.java index 52bf030..44d67ce 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiProperties.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiProperties.java @@ -1,12 +1,14 @@ -/* ****************************************************************************** +/* ************************************************************************************** * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * MIT License which is available at https://opensource.org/licenses/MIT * * SPDX-License-Identifier: MIT - ****************************************************************************** */ + ************************************************************************************** */ package org.eclipse.keypop.calypso.certificate; /** diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java index 56592b3..2662cee 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateStore.java @@ -1,12 +1,14 @@ -/* ****************************************************************************** +/* ************************************************************************************** * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * MIT License which is available at https://opensource.org/licenses/MIT * * SPDX-License-Identifier: MIT - ****************************************************************************** */ + ************************************************************************************** */ package org.eclipse.keypop.calypso.certificate; import java.security.interfaces.RSAPublicKey; diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateConsistencyException.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateConsistencyException.java index 4fdeaf9..7ed8d8b 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateConsistencyException.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateConsistencyException.java @@ -1,12 +1,14 @@ -/* ****************************************************************************** +/* ************************************************************************************** * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * MIT License which is available at https://opensource.org/licenses/MIT * * SPDX-License-Identifier: MIT - ****************************************************************************** */ + ************************************************************************************** */ package org.eclipse.keypop.calypso.certificate; /** diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateSigningException.java b/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateSigningException.java index ee92184..5b89143 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateSigningException.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/CertificateSigningException.java @@ -1,12 +1,14 @@ -/* ****************************************************************************** +/* ************************************************************************************** * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * MIT License which is available at https://opensource.org/licenses/MIT * * SPDX-License-Identifier: MIT - ****************************************************************************** */ + ************************************************************************************** */ package org.eclipse.keypop.calypso.certificate; /** diff --git a/src/main/java/org/eclipse/keypop/calypso/certificate/spi/CalypsoCertificateSignerSpi.java b/src/main/java/org/eclipse/keypop/calypso/certificate/spi/CalypsoCertificateSignerSpi.java index b6233da..c3dea4a 100644 --- a/src/main/java/org/eclipse/keypop/calypso/certificate/spi/CalypsoCertificateSignerSpi.java +++ b/src/main/java/org/eclipse/keypop/calypso/certificate/spi/CalypsoCertificateSignerSpi.java @@ -1,12 +1,14 @@ -/* ****************************************************************************** +/* ************************************************************************************** * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * MIT License which is available at https://opensource.org/licenses/MIT * * SPDX-License-Identifier: MIT - ****************************************************************************** */ + ************************************************************************************** */ package org.eclipse.keypop.calypso.certificate.spi; /** diff --git a/src/test/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiPropertiesTest.java b/src/test/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiPropertiesTest.java index bb339d2..7c49cec 100644 --- a/src/test/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiPropertiesTest.java +++ b/src/test/java/org/eclipse/keypop/calypso/certificate/CalypsoCertificateApiPropertiesTest.java @@ -1,12 +1,14 @@ -/* ****************************************************************************** +/* ************************************************************************************** * Copyright (c) 2024 Calypso Networks Association https://calypsonet.org/ * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * MIT License which is available at https://opensource.org/licenses/MIT * * SPDX-License-Identifier: MIT - ****************************************************************************** */ + ************************************************************************************** */ package org.eclipse.keypop.calypso.certificate; import static org.assertj.core.api.Assertions.assertThat; @@ -14,14 +16,14 @@ import java.io.FileInputStream; import java.io.InputStream; import java.util.Properties; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; public class CalypsoCertificateApiPropertiesTest { private static String libVersion; - @BeforeClass + @BeforeAll public static void beforeClass() throws Exception { InputStream inputStream = new FileInputStream("gradle.properties"); try { From 5c9486e39a8e6ef1f432569dc88b7fa329f88eed Mon Sep 17 00:00:00 2001 From: Andrei Cristea Date: Wed, 11 Mar 2026 15:21:24 +0100 Subject: [PATCH 13/13] docs: fix URLs typo --- README.md | 4 ++-- src/main/javadoc/overview.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b0c7e5e..2d4e7a6 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ on the Keypop website [keypop.org](https://keypop.org/). ## API documentation -API Javadoc is available [here](https://eclipse-keypop.github.io/keypop-calypso-certificate-java-api). +API Javadoc is available [here](https://docs.keypop.org/keypop-calypso-certificate-java-api). -API documentation and class diagram is available +UML class diagram is available [here](https://terminal-api.calypsonet.org/apis/calypsonet-terminal-calypso-certificate-api/). ## About the source code diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html index bc50404..7fc8efe 100644 --- a/src/main/javadoc/overview.html +++ b/src/main/javadoc/overview.html @@ -8,7 +8,7 @@

      Aligned with the Terminal Calypso Certificate UML specifications proposed by the Calypso Networks Association (available - here), + here), it defines the generic interfaces required to create Calypso certificates.