diff --git a/java/PUBLISHING.md b/java/PUBLISHING.md new file mode 100644 index 000000000..16e041ebd --- /dev/null +++ b/java/PUBLISHING.md @@ -0,0 +1,80 @@ +# Java Publishing (Maven Central) + +This doc describes how to publish the Java modules in `java/` to Maven Central under the `com.selfid` groupId using the **Sonatype Central Portal** flow. + +## Modules + +- `cid` +- `crypto` +- `gatekeeper` +- `keymaster` + +## Prerequisites + +1. Sonatype account with the **com.selfid** namespace verified (Central Portal). +2. A GPG signing key (ASCII-armored) for signing artifacts. +3. Access to **Central Portal**: https://central.sonatype.com/ + +## Configuration + +Publishing is configured in `java/build.gradle` and `java/gradle.properties`. + +- Group + version are set in `java/gradle.properties`. + - Example: `group=com.selfid`, `version=1.0.0` +- POM metadata is defined in `java/build.gradle` (name, description, SCM, licenses, developers). + +## Secrets (Environment Variables) + +Set these before publishing: + +- `SONATYPE_USERNAME` (Central Portal **User Token** username) +- `SONATYPE_PASSWORD` (Central Portal **User Token** password) +- `SIGNING_KEY` (ASCII-armored private key text) +- `SIGNING_PASSWORD` + +Optional: + +- `CENTRAL_PUBLISHING_TYPE` (default: `USER_MANAGED`) +- `CENTRAL_DEPLOYMENT_NAME` (default: `Self ID Java `) + +Example: + +```bash +export SONATYPE_USERNAME=your_token_username +export SONATYPE_PASSWORD=your_token_password +export SIGNING_KEY="$(cat /path/to/privatekey.asc)" +export SIGNING_PASSWORD=your_key_password +``` + +## Build and Publish (Central Portal) + +From `java/`: + +```bash +./gradlew clean +./gradlew publishCentral +``` + +This will: + +1. Publish all subprojects into a local **central bundle** directory +2. Generate required checksum files +3. Zip the bundle +4. Upload it to Central Portal + +### Publish a Single Module (bundle only) + +```bash +./gradlew :cid:publishMavenJavaPublicationToCentralBundleRepository +``` + +To upload the full bundle after that, still run: + +```bash +./gradlew uploadCentralBundle +``` + +## Release in Central Portal + +After upload, log into Central Portal and follow the deployment status. If `CENTRAL_PUBLISHING_TYPE` is `USER_MANAGED`, you will need to manually release the deployment there. + diff --git a/java/README.md b/java/README.md index f0ea0ca70..c693cd48b 100644 --- a/java/README.md +++ b/java/README.md @@ -9,17 +9,17 @@ Modules: - `keymaster` — wallet + credential operations. Compatibility and runtime -- JDK 17 is required (targeted bytecode and test runtime). +- JDK 11 is required (targeted bytecode and test runtime). - Dependencies are pure-Java artifacts (OkHttp, Jackson, Bouncy Castle, Tink, bitcoinj). - Gatekeeper default base URL is `http://localhost:4224` (see `GatekeeperClientOptions`). Quickstart (Gradle) ```gradle dependencies { - implementation("org.keychain:cid:") - implementation("org.keychain:crypto:") - implementation("org.keychain:gatekeeper:") - implementation("org.keychain:keymaster:") + implementation("com.selfid:cid:1.0.0") + implementation("com.selfid:crypto:1.0.0") + implementation("com.selfid:gatekeeper:1.0.0") + implementation("com.selfid:keymaster:1.0.0") } ``` @@ -30,12 +30,12 @@ import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.keychain.gatekeeper.GatekeeperClient; -import org.keychain.gatekeeper.GatekeeperClientOptions; -import org.keychain.keymaster.Keymaster; -import org.keychain.keymaster.model.WalletEncFile; -import org.keychain.keymaster.store.WalletJson; -import org.keychain.keymaster.store.WalletStore; +import com.selfid.gatekeeper.GatekeeperClient; +import com.selfid.gatekeeper.GatekeeperClientOptions; +import com.selfid.keymaster.Keymaster; +import com.selfid.keymaster.model.WalletEncFile; +import com.selfid.keymaster.store.WalletJson; +import com.selfid.keymaster.store.WalletStore; Path dataDir = Paths.get(System.getProperty("user.home"), ".keymaster"); WalletStore store = new WalletJson<>(WalletEncFile.class, dataDir, "wallet.json"); diff --git a/java/build.gradle b/java/build.gradle index 4c9b27e1c..ddd91c879 100644 --- a/java/build.gradle +++ b/java/build.gradle @@ -1,7 +1,6 @@ allprojects { repositories { mavenCentral() - maven { url = 'https://jitpack.io' } } } @@ -29,3 +28,138 @@ subprojects { testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.2' } } + +def publishableModules = ['cid', 'crypto', 'gatekeeper', 'keymaster'] +def centralBundleDir = rootProject.layout.buildDirectory.dir("central-bundle") + +configure(subprojects.findAll { it.name in publishableModules }) { + apply plugin: 'maven-publish' + apply plugin: 'signing' + + java { + withSourcesJar() + withJavadocJar() + } + + publishing { + publications { + mavenJava(MavenPublication) { + from components.java + artifactId = project.name + + pom { + name = "Self ID Java ${project.name}" + description = "Self ID Java ${project.name} module" + url = "https://selfid.com" + + licenses { + license { + name = "MIT License" + url = "https://opensource.org/licenses/MIT" + distribution = "repo" + } + } + + developers { + developer { + id = "selfid" + name = "Self ID" + organization = "Self ID" + organizationUrl = "https://selfid.com" + } + } + + scm { + url = "https://github.com/KeychainMDIP/kc" + connection = "scm:git:https://github.com/KeychainMDIP/kc.git" + developerConnection = "scm:git:https://github.com/KeychainMDIP/kc.git" + } + } + } + } + + repositories { + maven { + name = "CentralBundle" + url = centralBundleDir.get().asFile.toURI() + } + } + } + + signing { + def signingKey = findProperty("signingKey") ?: System.getenv("SIGNING_KEY") + def signingPassword = findProperty("signingPassword") ?: System.getenv("SIGNING_PASSWORD") + + if (signingKey && signingPassword) { + useInMemoryPgpKeys(signingKey, signingPassword) + } + + sign publishing.publications.mavenJava + } +} + +tasks.register("cleanCentralBundle", Delete) { + delete centralBundleDir +} + +tasks.register("publishCentralBundle") { + dependsOn "cleanCentralBundle" + dependsOn publishableModules.collect { ":${it}:publishMavenJavaPublicationToCentralBundleRepository" } +} + +tasks.register("zipCentralBundle", Zip) { + dependsOn "publishCentralBundle" + from centralBundleDir + archiveFileName = "central-bundle.zip" + destinationDirectory = rootProject.layout.buildDirectory.dir("central") +} + +tasks.register("uploadCentralBundle") { + dependsOn "zipCentralBundle" + doLast { + def zipFile = tasks.named("zipCentralBundle", Zip).get().archiveFile.get().asFile + if (!zipFile.exists()) { + throw new GradleException("Central bundle zip not found: ${zipFile}") + } + + def username = System.getenv("SONATYPE_USERNAME") + def password = System.getenv("SONATYPE_PASSWORD") + if (!username || !password) { + throw new GradleException("SONATYPE_USERNAME and SONATYPE_PASSWORD must be set for Central upload.") + } + + def token = "${username}:${password}".bytes.encodeBase64().toString() + + def publishingType = System.getenv("CENTRAL_PUBLISHING_TYPE") ?: "USER_MANAGED" + def deploymentName = System.getenv("CENTRAL_DEPLOYMENT_NAME") ?: "Self ID Java ${project.version}" + def url = "https://central.sonatype.com/api/v1/publisher/upload?publishingType=${publishingType}&name=${java.net.URLEncoder.encode(deploymentName, 'UTF-8')}" + + def stdout = new ByteArrayOutputStream() + def stderr = new ByteArrayOutputStream() + + def result = project.exec { + commandLine "curl", + "--fail", + "--silent", + "--show-error", + "--request", "POST", + "--header", "Authorization: Bearer ${token}", + "--form", "bundle=@${zipFile.absolutePath}", + url + standardOutput = stdout + errorOutput = stderr + ignoreExitValue = true + } + + if (result.exitValue != 0) { + throw new GradleException("Central upload failed: ${stderr.toString('UTF-8').trim()}") + } + + def deploymentId = stdout.toString("UTF-8").trim() + println("Central upload complete. Deployment ID: ${deploymentId}") + } +} + +tasks.register("publishCentral") { + dependsOn "uploadCentralBundle" +} diff --git a/java/cid/README.md b/java/cid/README.md index 02e1a27e7..8c146dadd 100644 --- a/java/cid/README.md +++ b/java/cid/README.md @@ -5,7 +5,7 @@ Minimal CID validator used by Keymaster for DID checks. ## Usage ```java -import org.keychain.cid.Cid; +import com.selfid.cid.Cid; boolean ok = Cid.isValid("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"); ``` diff --git a/java/crypto/README.md b/java/crypto/README.md index 47644ee72..527626879 100644 --- a/java/crypto/README.md +++ b/java/crypto/README.md @@ -5,10 +5,10 @@ Core crypto utilities that mirror the JS Keymaster behavior. ## Usage ```java -import org.keychain.crypto.HdKey; -import org.keychain.crypto.JwkPair; -import org.keychain.crypto.KeymasterCrypto; -import org.keychain.crypto.KeymasterCryptoImpl; +import com.selfid.crypto.HdKey; +import com.selfid.crypto.JwkPair; +import com.selfid.crypto.KeymasterCrypto; +import com.selfid.crypto.KeymasterCryptoImpl; KeymasterCrypto crypto = new KeymasterCryptoImpl(); diff --git a/java/crypto/build.gradle b/java/crypto/build.gradle index 4cc45e77a..ebab32037 100644 --- a/java/crypto/build.gradle +++ b/java/crypto/build.gradle @@ -2,6 +2,6 @@ dependencies { api 'org.bouncycastle:bcprov-jdk15to18:1.78.1' api 'com.google.crypto.tink:tink:1.12.0' api 'org.bitcoinj:bitcoinj-core:0.16.3' - api 'com.github.erdtman:java-json-canonicalization:1.1' + api 'io.github.erdtman:java-json-canonicalization:1.1' api 'com.fasterxml.jackson.core:jackson-databind:2.17.1' } diff --git a/java/gatekeeper/README.md b/java/gatekeeper/README.md index c08128fd1..10d2faba0 100644 --- a/java/gatekeeper/README.md +++ b/java/gatekeeper/README.md @@ -5,9 +5,9 @@ HTTP client for the Gatekeeper REST API. ## Usage ```java -import org.keychain.gatekeeper.GatekeeperClient; -import org.keychain.gatekeeper.GatekeeperClientOptions; -import org.keychain.gatekeeper.model.MdipDocument; +import com.selfid.gatekeeper.GatekeeperClient; +import com.selfid.gatekeeper.GatekeeperClientOptions; +import com.selfid.gatekeeper.model.MdipDocument; GatekeeperClientOptions options = new GatekeeperClientOptions(); options.baseUrl = "http://localhost:4224"; diff --git a/java/gradle.properties b/java/gradle.properties index 02c4f5d0f..1871a5429 100644 --- a/java/gradle.properties +++ b/java/gradle.properties @@ -1 +1,3 @@ org.gradle.java.home=/usr/lib/jvm/java-11-openjdk-amd64 +group=com.selfid +version=1.0.1 diff --git a/java/keymaster/README.md b/java/keymaster/README.md index 416bf628a..5103799bd 100644 --- a/java/keymaster/README.md +++ b/java/keymaster/README.md @@ -7,12 +7,12 @@ Keymaster manages an encrypted wallet and signs Gatekeeper operations. ```java import java.nio.file.Path; import java.nio.file.Paths; -import org.keychain.gatekeeper.GatekeeperClient; -import org.keychain.gatekeeper.GatekeeperClientOptions; -import org.keychain.keymaster.Keymaster; -import org.keychain.keymaster.model.WalletEncFile; -import org.keychain.keymaster.store.WalletJson; -import org.keychain.keymaster.store.WalletStore; +import com.selfid.gatekeeper.GatekeeperClient; +import com.selfid.gatekeeper.GatekeeperClientOptions; +import com.selfid.keymaster.Keymaster; +import com.selfid.keymaster.model.WalletEncFile; +import com.selfid.keymaster.store.WalletJson; +import com.selfid.keymaster.store.WalletStore; Path dataDir = Paths.get(System.getProperty("user.home"), ".keymaster"); WalletStore store = new WalletJson<>(WalletEncFile.class, dataDir, "wallet.json");