From 3318922008ea03242a830d591c95c273dc628ada Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Thu, 26 Mar 2026 16:31:49 +0530
Subject: [PATCH 01/28] JWT pre-flight check code
Signed-off-by: hrishikesh-nalawade
---
build.gradle | 1 +
pre-flight-check/build.gradle | 27 ++++
.../org/zowe/apiml/HttpClientWrapper.java | 65 ++++++++
.../org/zowe/apiml/JwkEndpointChecker.java | 100 ++++++++++++
.../java/org/zowe/apiml/PreFlightCheck.java | 70 +++++++++
.../org/zowe/apiml/PreFlightCheckConf.java | 106 +++++++++++++
.../org/zowe/apiml/PreFlightCheckConfig.java | 34 ++++
.../org/zowe/apiml/SSLContextFactory.java | 52 ++++++
.../src/main/java/org/zowe/apiml/Stores.java | 128 +++++++++++++++
.../apiml/StoresNotInitializeException.java | 18 +++
.../zowe/apiml/JwkEndpointCheckerTest.java | 148 ++++++++++++++++++
.../org/zowe/apiml/PreFlightCheckTest.java | 90 +++++++++++
settings.gradle | 1 +
13 files changed, 840 insertions(+)
create mode 100644 pre-flight-check/build.gradle
create mode 100644 pre-flight-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
create mode 100644 pre-flight-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
create mode 100644 pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheck.java
create mode 100644 pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConf.java
create mode 100644 pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConfig.java
create mode 100644 pre-flight-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
create mode 100644 pre-flight-check/src/main/java/org/zowe/apiml/Stores.java
create mode 100644 pre-flight-check/src/main/java/org/zowe/apiml/StoresNotInitializeException.java
create mode 100644 pre-flight-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
create mode 100644 pre-flight-check/src/test/java/org/zowe/apiml/PreFlightCheckTest.java
diff --git a/build.gradle b/build.gradle
index e8eb874ccb..c88fe4b4fe 100644
--- a/build.gradle
+++ b/build.gradle
@@ -183,6 +183,7 @@ ext.javaLibraries = [
'apiml-security-common',
'apiml-tomcat-common',
'certificate-analyser',
+ 'pre-flight-check',
'common-service-core',
'security-service-client-spring',
'apiml-sample-extension',
diff --git a/pre-flight-check/build.gradle b/pre-flight-check/build.gradle
new file mode 100644
index 0000000000..ca98a4c3d4
--- /dev/null
+++ b/pre-flight-check/build.gradle
@@ -0,0 +1,27 @@
+plugins {
+ id 'java'
+}
+
+dependencies {
+ implementation libs.picocli
+ annotationProcessor libs.picocli.codegen
+
+ testImplementation libs.mockito.core
+ testImplementation libs.hamcrest
+}
+
+compileJava {
+ options.compilerArgs += ["-Aproject=${project.group}/${project.name}"]
+}
+
+jar {
+ manifest {
+ attributes(
+ 'Main-Class': 'org.zowe.apiml.PreFlightCheck'
+ )
+ }
+ duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+ from {
+ configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
+ }
+}
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java b/pre-flight-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
new file mode 100644
index 0000000000..a8a091d9ad
--- /dev/null
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
@@ -0,0 +1,65 @@
+/*
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Copyright Contributors to the Zowe Project.
+ */
+
+package org.zowe.apiml;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Map;
+
+@SuppressWarnings("squid:S106")
+public class HttpClientWrapper {
+
+ private static final int CONNECT_TIMEOUT = 5000;
+ private static final int READ_TIMEOUT = 5000;
+
+ private final SSLContext sslContext;
+ private final boolean useHttps;
+
+ public HttpClientWrapper(SSLContext sslContext) {
+ this.sslContext = sslContext;
+ this.useHttps = true;
+ }
+
+ public HttpClientWrapper() {
+ this.sslContext = null;
+ this.useHttps = false;
+ }
+
+ public int executeCall(URL url, Map headers) throws IOException {
+ HttpURLConnection con;
+ if (useHttps) {
+ HttpsURLConnection httpsCon = (HttpsURLConnection) url.openConnection();
+ httpsCon.setSSLSocketFactory(sslContext.getSocketFactory());
+ con = httpsCon;
+ } else {
+ con = (HttpURLConnection) url.openConnection();
+ }
+
+ con.setRequestMethod("GET");
+ con.setConnectTimeout(CONNECT_TIMEOUT);
+ con.setReadTimeout(READ_TIMEOUT);
+
+ if (headers != null) {
+ for (Map.Entry entry : headers.entrySet()) {
+ con.setRequestProperty(entry.getKey(), entry.getValue());
+ }
+ }
+
+ try {
+ return con.getResponseCode();
+ } finally {
+ con.disconnect();
+ }
+ }
+}
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java b/pre-flight-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
new file mode 100644
index 0000000000..c9301b2084
--- /dev/null
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
@@ -0,0 +1,100 @@
+/*
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Copyright Contributors to the Zowe Project.
+ */
+
+package org.zowe.apiml;
+
+import javax.net.ssl.SSLHandshakeException;
+import java.net.ConnectException;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+@SuppressWarnings("squid:S106")
+public class JwkEndpointChecker {
+
+ static final String JWK_ENDPOINT_PATH = "/jwt/ibm/api/zOSMFBuilder/jwk";
+ private static final String ZOSMF_CSRF_HEADER = "X-CSRF-ZOSMF-HEADER";
+
+ private final HttpClientWrapper httpClient;
+ private final PreFlightCheckConfig conf;
+
+ public JwkEndpointChecker(HttpClientWrapper httpClient, PreFlightCheckConfig conf) {
+ this.httpClient = httpClient;
+ this.conf = conf;
+ }
+
+ public boolean check() {
+ String urlString = conf.getScheme() + "://" + conf.getZosmfHost() + ":" + conf.getZosmfPort() + JWK_ENDPOINT_PATH;
+
+ Map headers = new HashMap<>();
+ headers.put(ZOSMF_CSRF_HEADER, "");
+
+ try {
+ URL url = new URL(urlString);
+ System.out.println("Checking z/OSMF JWK endpoint: " + urlString);
+
+ int responseCode = httpClient.executeCall(url, headers);
+ return evaluateResponseCode(responseCode, urlString);
+ } catch (SSLHandshakeException e) {
+ System.err.println("FAILURE: SSL handshake failed when connecting to " + urlString + ".");
+ System.err.println("Verify that the truststore contains the z/OSMF server certificate.");
+ System.err.println("Details: " + e.getMessage());
+ return false;
+ } catch (ConnectException e) {
+ System.err.println("FAILURE: Cannot connect to " + conf.getZosmfHost() + ":" + conf.getZosmfPort() + ".");
+ System.err.println("Verify the host and port are correct and z/OSMF is running.");
+ System.err.println("Details: " + e.getMessage());
+ return false;
+ } catch (SocketTimeoutException e) {
+ System.err.println("FAILURE: Connection timed out to " + conf.getZosmfHost() + ":" + conf.getZosmfPort() + ".");
+ System.err.println("Details: " + e.getMessage());
+ return false;
+ } catch (Exception e) {
+ System.err.println("FAILURE: Unexpected error when calling " + urlString + ".");
+ System.err.println("Details: " + e.getMessage());
+ return false;
+ }
+ }
+
+ private boolean evaluateResponseCode(int responseCode, String urlString) {
+ if (responseCode >= 200 && responseCode < 300) {
+ System.out.println("SUCCESS: z/OSMF JWK endpoint is reachable and responding. HTTP " + responseCode);
+ return true;
+ }
+
+ if (responseCode == 401) {
+ System.out.println("SUCCESS: z/OSMF JWK endpoint exists (returned 401 Unauthorized — expected without credentials). HTTP 401");
+ return true;
+ }
+
+ if (responseCode == 404) {
+ System.err.println("FAILURE: z/OSMF JWK endpoint not found. HTTP 404");
+ System.err.println("Try configuring the jwtAutoConfiguration to LTPA");
+ return false;
+ }
+
+ if (responseCode >= 400 && responseCode < 500) {
+ System.err.println("FAILURE: z/OSMF JWK endpoint returned unexpected client error. HTTP " + responseCode);
+ System.err.println("URL: " + urlString);
+ return false;
+ }
+
+ if (responseCode >= 500) {
+ System.err.println("FAILURE: z/OSMF JWK endpoint returned server error. HTTP " + responseCode);
+ System.err.println("URL: " + urlString);
+ return false;
+ }
+
+ System.err.println("FAILURE: z/OSMF JWK endpoint returned unexpected response code. HTTP " + responseCode);
+ System.err.println("URL: " + urlString);
+ return false;
+ }
+}
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheck.java b/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheck.java
new file mode 100644
index 0000000000..0d4e30be85
--- /dev/null
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheck.java
@@ -0,0 +1,70 @@
+/*
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Copyright Contributors to the Zowe Project.
+ */
+
+package org.zowe.apiml;
+
+import picocli.CommandLine;
+
+@SuppressWarnings("squid:S106")
+public class PreFlightCheck {
+
+ public static int mainWithExitCode(String[] args) {
+ try {
+ PreFlightCheckConf conf = new PreFlightCheckConf();
+ CommandLine cmd = new CommandLine(conf);
+ cmd.parseArgs(args);
+
+ if (conf.isHelpRequested()) {
+ cmd.printVersionHelp(System.out);
+ CommandLine.usage(new PreFlightCheckConf(), System.out);
+ return 8;
+ }
+
+ validateConfig(conf);
+
+ HttpClientWrapper httpClient;
+ if ("https".equalsIgnoreCase(conf.getScheme())) {
+ Stores stores = new Stores(conf);
+ SSLContextFactory sslContextFactory = SSLContextFactory.initSSLContext(stores);
+ httpClient = new HttpClientWrapper(sslContextFactory.getSslContext());
+ } else {
+ httpClient = new HttpClientWrapper();
+ }
+
+ JwkEndpointChecker checker = new JwkEndpointChecker(httpClient, conf);
+ boolean success = checker.check();
+ return success ? 0 : 4;
+ } catch (Exception e) {
+ System.err.println("ERROR: " + e.getMessage());
+ return 4;
+ }
+ }
+
+ static void validateConfig(PreFlightCheckConf conf) {
+ String scheme = conf.getScheme();
+ if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) {
+ throw new IllegalArgumentException("--scheme must be 'http' or 'https', got: " + scheme);
+ }
+
+ if ("https".equalsIgnoreCase(scheme)) {
+ if (conf.getTrustStore() == null) {
+ throw new IllegalArgumentException("--truststore is required when --scheme=https. " +
+ "Provide the path to the truststore containing the z/OSMF server certificate.");
+ }
+ if (conf.getTrustStorePassword() == null) {
+ throw new IllegalArgumentException("--truststore-password is required when --scheme=https.");
+ }
+ }
+ }
+
+ public static void main(String[] args) {
+ System.exit(mainWithExitCode(args));
+ }
+}
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConf.java b/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConf.java
new file mode 100644
index 0000000000..2887736bb3
--- /dev/null
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConf.java
@@ -0,0 +1,106 @@
+/*
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Copyright Contributors to the Zowe Project.
+ */
+
+package org.zowe.apiml;
+
+import picocli.CommandLine;
+import picocli.CommandLine.Option;
+
+@CommandLine.Command(
+ name = "pre-flight-check",
+ version = {
+ "Pre-Flight Check 1.0",
+ "JVM: ${java.version} (${java.vendor} ${java.vm.name} ${java.vm.version})",
+ "OS: ${os.name} ${os.version} ${os.arch}"
+ },
+ description = "Performs a pre-flight connectivity check against the z/OSMF JWK endpoint."
+)
+public class PreFlightCheckConf implements PreFlightCheckConfig {
+
+ @Option(names = {"--zosmf-host"}, required = true, description = "Hostname or IP of the z/OSMF server")
+ private String zosmfHost;
+
+ @Option(names = {"--zosmf-port"}, required = true, description = "Port of the z/OSMF server")
+ private int zosmfPort;
+
+ @Option(names = {"--scheme"}, description = "http or https (default: ${DEFAULT-VALUE})")
+ private String scheme = "https";
+
+ @Option(names = {"--keystore"}, description = "Path to the keystore file (for HTTPS mutual TLS)")
+ private String keyStore;
+
+ @Option(names = {"--keystore-password"}, arity = "0..1", interactive = true, description = "Password for the keystore")
+ private String keyStorePassword;
+
+ @Option(names = {"--keystore-type"}, description = "Type of keystore (default: ${DEFAULT-VALUE})")
+ private String keyStoreType = "PKCS12";
+
+ @Option(names = {"--truststore"}, description = "Path to the truststore file (for HTTPS)")
+ private String trustStore;
+
+ @Option(names = {"--truststore-password"}, arity = "0..1", interactive = true, description = "Password for the truststore")
+ private String trustStorePassword;
+
+ @Option(names = {"--truststore-type"}, description = "Type of truststore (default: ${DEFAULT-VALUE})")
+ private String trustStoreType = "PKCS12";
+
+ @Option(names = {"-h", "--help"}, usageHelp = true, description = "Display a help message")
+ private boolean helpRequested = false;
+
+ @Override
+ public String getZosmfHost() {
+ return zosmfHost;
+ }
+
+ @Override
+ public int getZosmfPort() {
+ return zosmfPort;
+ }
+
+ @Override
+ public String getScheme() {
+ return scheme;
+ }
+
+ @Override
+ public String getKeyStore() {
+ return keyStore;
+ }
+
+ @Override
+ public String getKeyStorePassword() {
+ return keyStorePassword;
+ }
+
+ @Override
+ public String getKeyStoreType() {
+ return keyStoreType;
+ }
+
+ @Override
+ public String getTrustStore() {
+ return trustStore;
+ }
+
+ @Override
+ public String getTrustStorePassword() {
+ return trustStorePassword;
+ }
+
+ @Override
+ public String getTrustStoreType() {
+ return trustStoreType;
+ }
+
+ @Override
+ public boolean isHelpRequested() {
+ return helpRequested;
+ }
+}
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConfig.java b/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConfig.java
new file mode 100644
index 0000000000..aa9ee16040
--- /dev/null
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConfig.java
@@ -0,0 +1,34 @@
+/*
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Copyright Contributors to the Zowe Project.
+ */
+
+package org.zowe.apiml;
+
+public interface PreFlightCheckConfig {
+
+ String getZosmfHost();
+
+ int getZosmfPort();
+
+ String getScheme();
+
+ String getKeyStore();
+
+ String getKeyStorePassword();
+
+ String getKeyStoreType();
+
+ String getTrustStore();
+
+ String getTrustStorePassword();
+
+ String getTrustStoreType();
+
+ boolean isHelpRequested();
+}
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/SSLContextFactory.java b/pre-flight-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
new file mode 100644
index 0000000000..c11b148343
--- /dev/null
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
@@ -0,0 +1,52 @@
+/*
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Copyright Contributors to the Zowe Project.
+ */
+
+package org.zowe.apiml;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+import java.io.IOException;
+import java.security.*;
+import java.security.cert.CertificateException;
+
+public class SSLContextFactory {
+
+ private final Stores stores;
+ private SSLContext sslContext;
+
+ private SSLContextFactory(Stores stores) {
+ this.stores = stores;
+ }
+
+ public SSLContext getSslContext() {
+ return sslContext;
+ }
+
+ public static SSLContextFactory initSSLContext(Stores stores) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException, CertificateException, IOException {
+ SSLContextFactory factory = new SSLContextFactory(stores);
+
+ TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ trustFactory.init(stores.getTrustStore());
+
+ KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ if (stores.getKeyStore() != null) {
+ keyFactory.init(stores.getKeyStore(), stores.getConf().getKeyStorePassword().toCharArray());
+ } else {
+ KeyStore emptyKeystore = KeyStore.getInstance(KeyStore.getDefaultType());
+ emptyKeystore.load(null, null);
+ keyFactory.init(emptyKeystore, null);
+ }
+
+ factory.sslContext = SSLContext.getInstance("TLSv1.2");
+ factory.sslContext.init(keyFactory.getKeyManagers(), trustFactory.getTrustManagers(), new SecureRandom());
+ return factory;
+ }
+}
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/Stores.java b/pre-flight-check/src/main/java/org/zowe/apiml/Stores.java
new file mode 100644
index 0000000000..04041403fb
--- /dev/null
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/Stores.java
@@ -0,0 +1,128 @@
+/*
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Copyright Contributors to the Zowe Project.
+ */
+
+package org.zowe.apiml;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@SuppressWarnings("squid:S106")
+public class Stores {
+
+ private static final Pattern KEYRING_PATTERN = Pattern.compile("^(safkeyring[^:]*):/{2,4}([^/]+)/([^/]+)$");
+
+ private KeyStore keyStore;
+ private KeyStore trustStore;
+ private final PreFlightCheckConfig conf;
+
+ public Stores(PreFlightCheckConfig conf) {
+ this.conf = conf;
+ init();
+ }
+
+ public static boolean isKeyring(String input) {
+ if (input == null) return false;
+ Matcher matcher = KEYRING_PATTERN.matcher(input);
+ return matcher.matches();
+ }
+
+ public static String formatKeyringUrl(String input) {
+ if (input == null) return null;
+ Matcher matcher = KEYRING_PATTERN.matcher(input);
+ if (matcher.matches()) {
+ return matcher.group(1) + "://" + matcher.group(2) + "/" + matcher.group(3);
+ }
+ return input;
+ }
+
+ void init() {
+ try {
+ initKeystore();
+ if (trustStore == null) {
+ initTruststore();
+ }
+ } catch (FileNotFoundException e) {
+ throw new StoresNotInitializeException("Error while loading keystore file. Error message: " + e.getMessage() + "\n" +
+ "Possible solution: Verify correct path to the keystore. Change owner or permission to the keystore file.");
+ } catch (Exception e) {
+ throw new StoresNotInitializeException(e.getMessage());
+ }
+ }
+
+ private void initTruststore() throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
+ if (conf.getTrustStore() == null) {
+ System.out.println("No truststore specified, will use empty.");
+ try {
+ this.trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ this.trustStore.load(null, null);
+ } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
+ System.err.println(e.getMessage());
+ }
+ return;
+ }
+ try (InputStream trustStoreIStream = new FileInputStream(conf.getTrustStore())) {
+ this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
+ }
+ }
+
+ private void initKeystore() throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
+ if (conf.getKeyStore() == null) {
+ return;
+ }
+ if (isKeyring(conf.getKeyStore())) {
+ try (InputStream keyringIStream = keyRingUrl(conf.getKeyStore()).openStream()) {
+ this.keyStore = readKeyStore(keyringIStream, conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
+ this.trustStore = this.keyStore;
+ } catch (Exception e) {
+ throw new StoresNotInitializeException(e.getMessage());
+ }
+ } else {
+ try (InputStream keyStoreIStream = new FileInputStream(conf.getKeyStore())) {
+ this.keyStore = readKeyStore(keyStoreIStream, conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
+ }
+ }
+ }
+
+ public static KeyStore readKeyStore(InputStream is, char[] pass, String type) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
+ KeyStore keyStore = KeyStore.getInstance(type);
+ keyStore.load(is, pass);
+ return keyStore;
+ }
+
+ public KeyStore getKeyStore() {
+ return keyStore;
+ }
+
+ public KeyStore getTrustStore() {
+ return trustStore;
+ }
+
+ public PreFlightCheckConfig getConf() {
+ return conf;
+ }
+
+ public static URL keyRingUrl(String uri) throws MalformedURLException {
+ if (!isKeyring(uri)) {
+ throw new StoresNotInitializeException("Incorrect key ring format: " + uri
+ + ". Make sure you use format safkeyring://userId/keyRing");
+ }
+ return new URL(formatKeyringUrl(uri));
+ }
+}
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/StoresNotInitializeException.java b/pre-flight-check/src/main/java/org/zowe/apiml/StoresNotInitializeException.java
new file mode 100644
index 0000000000..e5c77f773c
--- /dev/null
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/StoresNotInitializeException.java
@@ -0,0 +1,18 @@
+/*
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Copyright Contributors to the Zowe Project.
+ */
+
+package org.zowe.apiml;
+
+public class StoresNotInitializeException extends RuntimeException {
+
+ public StoresNotInitializeException(String message) {
+ super(message);
+ }
+}
diff --git a/pre-flight-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java b/pre-flight-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
new file mode 100644
index 0000000000..642f4d507c
--- /dev/null
+++ b/pre-flight-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
@@ -0,0 +1,148 @@
+/*
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Copyright Contributors to the Zowe Project.
+ */
+
+package org.zowe.apiml;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import javax.net.ssl.SSLHandshakeException;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.ConnectException;
+import java.net.SocketTimeoutException;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class JwkEndpointCheckerTest {
+
+ private final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ private final ByteArrayOutputStream errStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+ private final PrintStream originalErr = System.err;
+
+ private HttpClientWrapper mockClient;
+ private PreFlightCheckConfig mockConf;
+
+ @BeforeEach
+ void setUp() {
+ System.setOut(new PrintStream(outStream));
+ System.setErr(new PrintStream(errStream));
+
+ mockClient = mock(HttpClientWrapper.class);
+ mockConf = mock(PreFlightCheckConfig.class);
+ when(mockConf.getScheme()).thenReturn("https");
+ when(mockConf.getZosmfHost()).thenReturn("zosmf.example.com");
+ when(mockConf.getZosmfPort()).thenReturn(443);
+ }
+
+ @AfterEach
+ void restoreStreams() {
+ System.setOut(originalOut);
+ System.setErr(originalErr);
+ }
+
+ @Nested
+ class SuccessResponses {
+
+ @Test
+ void response200IsSuccess() throws IOException {
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(200);
+ JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
+ assertTrue(checker.check());
+ assertTrue(outStream.toString().contains("SUCCESS"));
+ assertTrue(outStream.toString().contains("200"));
+ }
+
+ @Test
+ void response401IsSuccess() throws IOException {
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(401);
+ JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
+ assertTrue(checker.check());
+ assertTrue(outStream.toString().contains("SUCCESS"));
+ assertTrue(outStream.toString().contains("401"));
+ }
+ }
+
+ @Nested
+ class FailureResponses {
+
+ @Test
+ void response404IsFailure() throws IOException {
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(404);
+ JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
+ assertFalse(checker.check());
+ assertTrue(errStream.toString().contains("FAILURE"));
+ assertTrue(errStream.toString().contains("404"));
+ }
+
+ @Test
+ void response500IsFailure() throws IOException {
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(500);
+ JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
+ assertFalse(checker.check());
+ assertTrue(errStream.toString().contains("FAILURE"));
+ assertTrue(errStream.toString().contains("server error"));
+ }
+
+ @Test
+ void response403IsFailure() throws IOException {
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(403);
+ JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
+ assertFalse(checker.check());
+ assertTrue(errStream.toString().contains("FAILURE"));
+ assertTrue(errStream.toString().contains("client error"));
+ }
+ }
+
+ @Nested
+ class ExceptionHandling {
+
+ @Test
+ void sslHandshakeExceptionReportsCertificateError() throws IOException {
+ when(mockClient.executeCall(any(), anyMap())).thenThrow(new SSLHandshakeException("certificate unknown"));
+ JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
+ assertFalse(checker.check());
+ assertTrue(errStream.toString().contains("SSL handshake failed"));
+ assertTrue(errStream.toString().contains("truststore"));
+ }
+
+ @Test
+ void connectExceptionReportsUnreachable() throws IOException {
+ when(mockClient.executeCall(any(), anyMap())).thenThrow(new ConnectException("Connection refused"));
+ JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
+ assertFalse(checker.check());
+ assertTrue(errStream.toString().contains("Cannot connect"));
+ }
+
+ @Test
+ void socketTimeoutExceptionReportsTimeout() throws IOException {
+ when(mockClient.executeCall(any(), anyMap())).thenThrow(new SocketTimeoutException("Read timed out"));
+ JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
+ assertFalse(checker.check());
+ assertTrue(errStream.toString().contains("timed out"));
+ }
+
+ @Test
+ void unexpectedExceptionIsHandled() throws IOException {
+ when(mockClient.executeCall(any(), anyMap())).thenThrow(new IOException("unexpected"));
+ JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
+ assertFalse(checker.check());
+ assertTrue(errStream.toString().contains("Unexpected error"));
+ }
+ }
+}
diff --git a/pre-flight-check/src/test/java/org/zowe/apiml/PreFlightCheckTest.java b/pre-flight-check/src/test/java/org/zowe/apiml/PreFlightCheckTest.java
new file mode 100644
index 0000000000..a361a75819
--- /dev/null
+++ b/pre-flight-check/src/test/java/org/zowe/apiml/PreFlightCheckTest.java
@@ -0,0 +1,90 @@
+/*
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Copyright Contributors to the Zowe Project.
+ */
+
+package org.zowe.apiml;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class PreFlightCheckTest {
+
+ private final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ private final ByteArrayOutputStream errStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+ private final PrintStream originalErr = System.err;
+
+ @BeforeEach
+ void setupStreams() {
+ System.setOut(new PrintStream(outStream));
+ System.setErr(new PrintStream(errStream));
+ }
+
+ @AfterEach
+ void restoreStreams() {
+ System.setOut(originalOut);
+ System.setErr(originalErr);
+ }
+
+ @Test
+ void helpFlagReturnsExitCode8() {
+ String[] args = {"--help"};
+ assertEquals(8, PreFlightCheck.mainWithExitCode(args));
+ assertTrue(outStream.toString().contains("Pre-Flight Check"));
+ }
+
+ @Test
+ void missingRequiredArgsReturnsExitCode4() {
+ String[] args = {};
+ assertEquals(4, PreFlightCheck.mainWithExitCode(args));
+ }
+
+ @Nested
+ class ValidationTests {
+
+ @Test
+ void invalidSchemeIsRejected() {
+ String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "443", "--scheme", "ftp"};
+ assertEquals(4, PreFlightCheck.mainWithExitCode(args));
+ assertTrue(errStream.toString().contains("--scheme must be 'http' or 'https'"));
+ }
+
+ @Test
+ void httpsWithoutTruststoreIsRejected() {
+ String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "443", "--scheme", "https"};
+ assertEquals(4, PreFlightCheck.mainWithExitCode(args));
+ assertTrue(errStream.toString().contains("--truststore is required"));
+ }
+
+ @Test
+ void httpsWithoutTruststorePasswordIsRejected() {
+ String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "443", "--scheme", "https",
+ "--truststore", "some/path.p12"};
+ assertEquals(4, PreFlightCheck.mainWithExitCode(args));
+ assertTrue(errStream.toString().contains("--truststore-password is required"));
+ }
+
+ @Test
+ void httpDoesNotRequireTruststore() {
+ // This will fail to connect but should not fail validation
+ String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "19999", "--scheme", "http"};
+ int exitCode = PreFlightCheck.mainWithExitCode(args);
+ // Should be 4 (connection failure) not a validation error
+ assertEquals(4, exitCode);
+ assertFalse(errStream.toString().contains("--truststore is required"));
+ }
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index 2fadb0f914..8c4a32051f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -54,6 +54,7 @@ include 'onboarding-enabler-python'
include 'zaas-client'
include 'mock-services'
include 'certificate-analyser'
+include 'pre-flight-check'
include 'apiml-tomcat-common'
include 'apiml-sample-extension'
include 'apiml-sample-extension-package'
From 2fafc3caa304a301c5db960743e7adbc810cf4cc Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Fri, 27 Mar 2026 16:12:09 +0530
Subject: [PATCH 02/28] handling verifyCertificate Scenarios
Signed-off-by: hrishikesh-nalawade
---
pre-flight-check/README.md | 331 ++++++++++++++++++
.../org/zowe/apiml/HttpClientWrapper.java | 9 +-
.../java/org/zowe/apiml/PreFlightCheck.java | 39 ++-
.../org/zowe/apiml/PreFlightCheckConf.java | 8 +
.../org/zowe/apiml/PreFlightCheckConfig.java | 2 +
.../org/zowe/apiml/SSLContextFactory.java | 34 ++
.../org/zowe/apiml/PreFlightCheckTest.java | 30 +-
7 files changed, 445 insertions(+), 8 deletions(-)
create mode 100644 pre-flight-check/README.md
diff --git a/pre-flight-check/README.md b/pre-flight-check/README.md
new file mode 100644
index 0000000000..b84b00b87c
--- /dev/null
+++ b/pre-flight-check/README.md
@@ -0,0 +1,331 @@
+# Pre-Flight Check Tool
+
+A Java utility that verifies connectivity to the z/OSMF JWK endpoint **before/after** starting the Zowe API Mediation Layer. This tool helps diagnose configuration issues early such as incorrect hostnames, unreachable ports, missing certificates, or misconfigured z/OSMF by performing a lightweight HTTP(S) call to the z/OSMF JWK endpoint at `/jwt/ibm/api/zOSMFBuilder/jwk`.
+
+## Table of Contents
+
+- [Overview](#overview)
+- [Prerequisites](#prerequisites)
+- [Building](#building)
+- [Usage](#usage)
+- [CLI Flags Reference](#cli-flags-reference)
+- [Certificate Verification Modes](#certificate-verification-modes)
+- [Exit Codes](#exit-codes)
+- [Response Interpretation](#response-interpretation)
+- [Testing Scenarios](#testing-scenarios)
+ - [1. Quick Test — DISABLED Mode](#1-quick-test--disabled-mode-no-truststore-needed)
+ - [2. STRICT Mode — Full Verification](#2-strict-mode--full-certificate-and-hostname-verification)
+ - [3. NONSTRICT Mode — Skip Hostname Check](#3-nonstrict-mode--certificate-chain-verified-hostname-check-skipped)
+ - [4. HTTP Mode (No SSL)](#4-http-mode-no-ssl)
+ - [5. Validation Error Tests](#5-validation-error-tests)
+- [SAF Keyrings](#saf-keyrings)
+- [Troubleshooting](#troubleshooting)
+
+---
+
+## Overview
+
+When Zowe API ML starts, it attempts to reach z/OSMF to obtain public keys for JWT token validation. If z/OSMF is unreachable or misconfigured, the startup fails with errors that can be difficult to diagnose. This pre-flight check tool isolates that connectivity test into a simple, standalone JAR that can be run before Zowe startup.
+
+**What it checks:**
+
+- TCP connectivity to the z/OSMF host and port
+- SSL/TLS handshake (when using HTTPS)
+- Certificate trust chain validation (STRICT/NONSTRICT modes)
+- Hostname verification (STRICT mode)
+- HTTP response from the JWK endpoint (`/jwt/ibm/api/zOSMFBuilder/jwk`)
+
+## Prerequisites
+
+- **Java 17 or higher** (Java 17, 21, or any later version)
+- Network access to the z/OSMF server
+- A truststore containing the z/OSMF server's CA certificate (required for STRICT and NONSTRICT modes)
+
+## Building
+
+From the root of the `api-layer` repository:
+
+```bash
+./gradlew :pre-flight-check:build
+```
+
+On Windows:
+
+```powershell
+.\gradlew :pre-flight-check:build
+```
+
+The fat JAR (with all dependencies bundled) will be generated at:
+
+```
+pre-flight-check/build/libs/pre-flight-check-.jar
+```
+
+For example: `pre-flight-check/build/libs/pre-flight-check-3.5.12-SNAPSHOT.jar`
+
+## Usage
+
+```bash
+java -jar pre-flight-check-.jar --zosmf-host --zosmf-port [options]
+```
+
+**Minimal example (DISABLED mode — quickest way to test):**
+
+```bash
+java -jar pre-flight-check-.jar \
+ --zosmf-host myzosmf.example.com \
+ --zosmf-port 11443 \
+ --verify-certificates DISABLED
+```
+
+**Full example (STRICT mode with truststore):**
+
+```bash
+java -jar pre-flight-check-.jar \
+ --zosmf-host myzosmf.example.com \
+ --zosmf-port 11443 \
+ --truststore /path/to/truststore.p12 \
+ --truststore-password changeit
+```
+
+**Display help:**
+
+```bash
+java -jar pre-flight-check-.jar --help
+```
+
+## CLI Flags Reference
+
+### Required Flags
+
+| Flag | Description | Example |
+|------|-------------|---------|
+| `--zosmf-host` | Hostname or IP address of the z/OSMF server | `--zosmf-host myzosmf.example.com` |
+| `--zosmf-port` | Port number of the z/OSMF server | `--zosmf-port 11443` |
+
+> **Note:** If `--zosmf-host` or `--zosmf-port` are omitted, picocli will display:
+> `Missing required option: '--zosmf-host='`
+
+### Conditionally Required Flags
+
+These flags are required when `--scheme=https` (the default) and `--verify-certificates` is **not** `DISABLED`:
+
+| Flag | Description | Error when missing |
+|------|-------------|-------------------|
+| `--truststore` | Path to the truststore file containing the z/OSMF CA certificate | `ERROR: --truststore is required when --scheme=https and verification is not DISABLED.` |
+| `--truststore-password` | Password for the truststore. If specified without a value, you will be prompted interactively. | `ERROR: --truststore-password is required when --scheme=https and verification is not DISABLED.` |
+
+### Optional Flags
+
+| Flag | Default | Description |
+|------|---------|-------------|
+| `--scheme` | `https` | Protocol to use: `http` or `https` |
+| `--verify-certificates` | `STRICT` | Certificate verification mode: `STRICT`, `NONSTRICT`, or `DISABLED` |
+| `--truststore-type` | `PKCS12` | Format of the truststore file (e.g., `PKCS12`, `JKS`, `JCERACFKS`) |
+| `--keystore` | *(none)* | Path to keystore file (only needed for mutual TLS / client certificate authentication) |
+| `--keystore-password` | *(none)* | Password for the keystore. If specified without a value, you will be prompted interactively. |
+| `--keystore-type` | `PKCS12` | Format of the keystore file |
+| `-h`, `--help` | | Display usage help and exit |
+
+## Certificate Verification Modes
+
+The `--verify-certificates` flag controls how SSL/TLS certificates are validated when connecting over HTTPS. This mirrors the `zowe.verifyCertificates` setting in the Zowe configuration (`zowe.yaml`).
+
+### STRICT (Default)
+
+```bash
+--verify-certificates STRICT
+```
+
+- **Certificate chain**: Fully validated against the truststore
+- **Hostname verification**: The server certificate's CN/SAN must match the `--zosmf-host` value
+- **Truststore**: Required
+- **Use case**: Production environments — maximum security
+
+### NONSTRICT
+
+```bash
+--verify-certificates NONSTRICT
+```
+
+- **Certificate chain**: Fully validated against the truststore
+- **Hostname verification**: Skipped — the server certificate does not need to match the hostname
+- **Truststore**: Required
+- **Use case**: Environments where the z/OSMF certificate is issued for a different hostname (e.g., accessing via IP address when the cert has a DNS name)
+
+### DISABLED
+
+```bash
+--verify-certificates DISABLED
+```
+
+- **Certificate chain**: Not validated — all certificates are trusted
+- **Hostname verification**: Skipped
+- **Truststore**: Not required
+- **Use case**: Development/testing environments, or initial connectivity debugging
+- **Warning**: Prints `WARNING: SSL certificate verification is DISABLED. All certificates will be trusted.`
+
+> **Security Note:** `DISABLED` mode should **never** be used in production. It is vulnerable to man-in-the-middle attacks.
+
+## Exit Codes
+
+| Code | Meaning |
+|------|---------|
+| `0` | **Success** — z/OSMF JWK endpoint is reachable and responding |
+| `4` | **Failure** — connection failed, SSL error, endpoint not found, or configuration error |
+| `8` | **Help** — help/version was displayed; no check was performed |
+
+## Response Interpretation
+
+The tool interprets HTTP response codes from the z/OSMF JWK endpoint as follows:
+
+| HTTP Code | Result | Message |
+|-----------|--------|---------|
+| 200-299 | **SUCCESS** | `z/OSMF JWK endpoint is reachable and responding. HTTP ` |
+| 401 | **SUCCESS** | `z/OSMF JWK endpoint exists (returned 401 Unauthorized — expected without credentials). HTTP 401` |
+| 404 | **FAILURE** | `z/OSMF JWK endpoint not found. HTTP 404` — Consider configuring `jwtAutoConfiguration` to LTPA |
+| 4xx (other) | **FAILURE** | `z/OSMF JWK endpoint returned unexpected client error. HTTP ` |
+| 5xx | **FAILURE** | `z/OSMF JWK endpoint returned server error. HTTP ` |
+
+**Note:** A `401 Unauthorized` is treated as **success** because the tool does not send authentication credentials. A 401 confirms the endpoint exists and z/OSMF is processing requests.
+
+### Connection-Level Errors
+
+| Error | Message |
+|-------|---------|
+| SSL handshake failure | `FAILURE: SSL handshake failed. Verify that the truststore contains the z/OSMF server certificate.` |
+| Connection refused | `FAILURE: Cannot connect to :. Verify the host and port are correct and z/OSMF is running.` |
+| Connection timeout | `FAILURE: Connection timed out to :.` |
+
+## Testing Scenarios
+
+Below are step-by-step commands for testing all modes. Replace `` with your actual JAR version (e.g., `3.5.12-SNAPSHOT`) and adjust the host/port for your environment.
+
+### 1. Quick Test — DISABLED Mode (No Truststore Needed)
+
+The fastest way to verify basic TCP + HTTP connectivity:
+
+```bash
+java -jar pre-flight-check/build/libs/pre-flight-check-.jar \
+ --zosmf-host myzosmf.example.com \
+ --zosmf-port 11443 \
+ --verify-certificates DISABLED
+```
+
+**Expected output (success):**
+
+```
+WARNING: SSL certificate verification is DISABLED. All certificates will be trusted.
+Checking z/OSMF JWK endpoint: https://myzosmf.example.com:11443/jwt/ibm/api/zOSMFBuilder/jwk
+SUCCESS: z/OSMF JWK endpoint exists (returned 401 Unauthorized — expected without credentials). HTTP 401
+```
+
+### 2. STRICT Mode — Full Certificate and Hostname Verification
+
+Requires a truststore containing the z/OSMF server's CA certificate (see [Creating a Truststore](#creating-a-truststore)):
+
+```bash
+java -jar pre-flight-check/build/libs/pre-flight-check-.jar \
+ --zosmf-host myzosmf.example.com \
+ --zosmf-port 11443 \
+ --truststore /path/to/zosmf-truststore.p12 \
+ --truststore-password password
+```
+
+**Expected output (success):**
+
+```
+Checking z/OSMF JWK endpoint: https://myzosmf.example.com:11443/jwt/ibm/api/zOSMFBuilder/jwk
+SUCCESS: z/OSMF JWK endpoint exists (returned 401 Unauthorized — expected without credentials). HTTP 401
+```
+
+**Expected output (SSL failure — wrong truststore):**
+
+```
+FAILURE: SSL handshake failed when connecting to https://myzosmf.example.com:11443/jwt/ibm/api/zOSMFBuilder/jwk.
+Verify that the truststore contains the z/OSMF server certificate.
+Details: PKIX path building failed: ...unable to find valid certification path to requested target
+```
+
+### 3. NONSTRICT Mode — Certificate Chain Verified, Hostname Check Skipped
+
+Useful when connecting via IP address but the certificate has a DNS name:
+
+```bash
+java -jar pre-flight-check/build/libs/pre-flight-check-.jar \
+ --zosmf-host 10.0.0.50 \
+ --zosmf-port 11443 \
+ --truststore /path/to/zosmf-truststore.p12 \
+ --truststore-password password \
+ --verify-certificates NONSTRICT
+```
+
+**Expected output (success):**
+
+```
+INFO: Hostname verification is disabled (NONSTRICT mode).
+Checking z/OSMF JWK endpoint: https://10.0.0.50:11443/jwt/ibm/api/zOSMFBuilder/jwk
+SUCCESS: z/OSMF JWK endpoint exists (returned 401 Unauthorized — expected without credentials). HTTP 401
+```
+
+### 4. HTTP Mode (No SSL)
+
+For z/OSMF instances running on plain HTTP (uncommon):
+
+```bash
+java -jar pre-flight-check/build/libs/pre-flight-check-.jar \
+ --zosmf-host myzosmf.example.com \
+ --zosmf-port 80 \
+ --scheme http
+```
+
+### 5. Validation Error Tests
+
+**Missing required flags:**
+
+```bash
+# No arguments at all
+java -jar pre-flight-check-.jar
+# Output: Missing required options: '--zosmf-host=', '--zosmf-port='
+
+# Missing truststore in STRICT mode
+java -jar pre-flight-check-.jar --zosmf-host myhost --zosmf-port 443
+# Output: ERROR: --truststore is required when --scheme=https and verification is not DISABLED.
+
+# Missing truststore password
+java -jar pre-flight-check-.jar --zosmf-host myhost --zosmf-port 443 --truststore my.p12
+# Output: ERROR: --truststore-password is required when --scheme=https and verification is not DISABLED.
+```
+
+**Invalid values:**
+
+```bash
+# Invalid scheme
+java -jar pre-flight-check-.jar --zosmf-host myhost --zosmf-port 443 --scheme ftp
+# Output: ERROR: --scheme must be 'http' or 'https', got: ftp
+
+# Invalid verify mode
+java -jar pre-flight-check-.jar --zosmf-host myhost --zosmf-port 443 --verify-certificates INVALID
+# Output: ERROR: --verify-certificates must be STRICT, NONSTRICT, or DISABLED, got: INVALID
+```
+
+**Unreachable host:**
+
+```bash
+java -jar pre-flight-check-.jar --zosmf-host nonexistent.host --zosmf-port 443 --verify-certificates DISABLED
+# Output: FAILURE: Cannot connect to nonexistent.host:443.
+```
+
+## SAF Keyrings
+
+On z/OS, if you are using SAF keyrings instead of file-based keystores/truststores, provide the keyring path in the `safkeyring://` format and add the JVM protocol handler:
+
+```bash
+java -Djava.protocol.handler.pkgs=com.ibm.crypto.provider \
+ -jar pre-flight-check-.jar \
+ --zosmf-host myzosmf.example.com \
+ --zosmf-port 11443 \
+ --truststore safkeyring://IZUSVR/ZoweKeyring \
+ --truststore-password password \
+ --truststore-type JCERACFKS
+```
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java b/pre-flight-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
index a8a091d9ad..df3fa0d5e5 100644
--- a/pre-flight-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
@@ -10,6 +10,7 @@
package org.zowe.apiml;
+import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import java.io.IOException;
@@ -25,15 +26,18 @@ public class HttpClientWrapper {
private final SSLContext sslContext;
private final boolean useHttps;
+ private final HostnameVerifier hostnameVerifier;
- public HttpClientWrapper(SSLContext sslContext) {
+ public HttpClientWrapper(SSLContext sslContext, HostnameVerifier hostnameVerifier) {
this.sslContext = sslContext;
this.useHttps = true;
+ this.hostnameVerifier = hostnameVerifier;
}
public HttpClientWrapper() {
this.sslContext = null;
this.useHttps = false;
+ this.hostnameVerifier = null;
}
public int executeCall(URL url, Map headers) throws IOException {
@@ -41,6 +45,9 @@ public int executeCall(URL url, Map headers) throws IOException
if (useHttps) {
HttpsURLConnection httpsCon = (HttpsURLConnection) url.openConnection();
httpsCon.setSSLSocketFactory(sslContext.getSocketFactory());
+ if (hostnameVerifier != null) {
+ httpsCon.setHostnameVerifier(hostnameVerifier);
+ }
con = httpsCon;
} else {
con = (HttpURLConnection) url.openConnection();
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheck.java b/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheck.java
index 0d4e30be85..034860b9f1 100644
--- a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheck.java
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheck.java
@@ -12,9 +12,15 @@
import picocli.CommandLine;
+import javax.net.ssl.HostnameVerifier;
+
@SuppressWarnings("squid:S106")
public class PreFlightCheck {
+ static final String VERIFY_STRICT = "STRICT";
+ static final String VERIFY_NONSTRICT = "NONSTRICT";
+ static final String VERIFY_DISABLED = "DISABLED";
+
public static int mainWithExitCode(String[] args) {
try {
PreFlightCheckConf conf = new PreFlightCheckConf();
@@ -31,9 +37,25 @@ public static int mainWithExitCode(String[] args) {
HttpClientWrapper httpClient;
if ("https".equalsIgnoreCase(conf.getScheme())) {
- Stores stores = new Stores(conf);
- SSLContextFactory sslContextFactory = SSLContextFactory.initSSLContext(stores);
- httpClient = new HttpClientWrapper(sslContextFactory.getSslContext());
+ String verifyMode = conf.getVerifyCertificates().toUpperCase();
+
+ if (VERIFY_DISABLED.equals(verifyMode)) {
+ SSLContextFactory sslContextFactory = SSLContextFactory.initTrustAllSSLContext();
+ HostnameVerifier noopVerifier = (hostname, session) -> true;
+ httpClient = new HttpClientWrapper(sslContextFactory.getSslContext(), noopVerifier);
+ } else {
+ Stores stores = new Stores(conf);
+ SSLContextFactory sslContextFactory = SSLContextFactory.initSSLContext(stores);
+
+ HostnameVerifier hostnameVerifier;
+ if (VERIFY_NONSTRICT.equals(verifyMode)) {
+ hostnameVerifier = (hostname, session) -> true;
+ System.out.println("INFO: Hostname verification is disabled (NONSTRICT mode).");
+ } else {
+ hostnameVerifier = null; // use default JDK hostname verifier
+ }
+ httpClient = new HttpClientWrapper(sslContextFactory.getSslContext(), hostnameVerifier);
+ }
} else {
httpClient = new HttpClientWrapper();
}
@@ -53,13 +75,18 @@ static void validateConfig(PreFlightCheckConf conf) {
throw new IllegalArgumentException("--scheme must be 'http' or 'https', got: " + scheme);
}
- if ("https".equalsIgnoreCase(scheme)) {
+ String verifyMode = conf.getVerifyCertificates().toUpperCase();
+ if (!VERIFY_STRICT.equals(verifyMode) && !VERIFY_NONSTRICT.equals(verifyMode) && !VERIFY_DISABLED.equals(verifyMode)) {
+ throw new IllegalArgumentException("--verify-certificates must be STRICT, NONSTRICT, or DISABLED, got: " + conf.getVerifyCertificates());
+ }
+
+ if ("https".equalsIgnoreCase(scheme) && !VERIFY_DISABLED.equals(verifyMode)) {
if (conf.getTrustStore() == null) {
- throw new IllegalArgumentException("--truststore is required when --scheme=https. " +
+ throw new IllegalArgumentException("--truststore is required when --scheme=https and verification is not DISABLED. " +
"Provide the path to the truststore containing the z/OSMF server certificate.");
}
if (conf.getTrustStorePassword() == null) {
- throw new IllegalArgumentException("--truststore-password is required when --scheme=https.");
+ throw new IllegalArgumentException("--truststore-password is required when --scheme=https and verification is not DISABLED.");
}
}
}
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConf.java b/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConf.java
index 2887736bb3..106c4c664c 100644
--- a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConf.java
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConf.java
@@ -51,6 +51,9 @@ public class PreFlightCheckConf implements PreFlightCheckConfig {
@Option(names = {"--truststore-type"}, description = "Type of truststore (default: ${DEFAULT-VALUE})")
private String trustStoreType = "PKCS12";
+ @Option(names = {"--verify-certificates"}, description = "Certificate verification mode: STRICT, NONSTRICT, or DISABLED (default: ${DEFAULT-VALUE})")
+ private String verifyCertificates = "STRICT";
+
@Option(names = {"-h", "--help"}, usageHelp = true, description = "Display a help message")
private boolean helpRequested = false;
@@ -99,6 +102,11 @@ public String getTrustStoreType() {
return trustStoreType;
}
+ @Override
+ public String getVerifyCertificates() {
+ return verifyCertificates;
+ }
+
@Override
public boolean isHelpRequested() {
return helpRequested;
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConfig.java b/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConfig.java
index aa9ee16040..0f0bb1e215 100644
--- a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConfig.java
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConfig.java
@@ -30,5 +30,7 @@ public interface PreFlightCheckConfig {
String getTrustStoreType();
+ String getVerifyCertificates();
+
boolean isHelpRequested();
}
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/SSLContextFactory.java b/pre-flight-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
index c11b148343..480d6f03e8 100644
--- a/pre-flight-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
@@ -12,11 +12,15 @@
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.security.*;
import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+@SuppressWarnings("squid:S106")
public class SSLContextFactory {
private final Stores stores;
@@ -49,4 +53,34 @@ public static SSLContextFactory initSSLContext(Stores stores) throws NoSuchAlgor
factory.sslContext.init(keyFactory.getKeyManagers(), trustFactory.getTrustManagers(), new SecureRandom());
return factory;
}
+
+ public static SSLContextFactory initTrustAllSSLContext() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, CertificateException, IOException, UnrecoverableKeyException {
+ SSLContextFactory factory = new SSLContextFactory(null);
+
+ TrustManager[] trustAllCerts = new TrustManager[]{
+ new X509TrustManager() {
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+
+ public void checkClientTrusted(X509Certificate[] certs, String authType) {
+ // trust all
+ }
+
+ public void checkServerTrusted(X509Certificate[] certs, String authType) {
+ // trust all
+ }
+ }
+ };
+
+ KeyStore emptyKeystore = KeyStore.getInstance(KeyStore.getDefaultType());
+ emptyKeystore.load(null, null);
+ KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ keyFactory.init(emptyKeystore, null);
+
+ factory.sslContext = SSLContext.getInstance("TLSv1.2");
+ factory.sslContext.init(keyFactory.getKeyManagers(), trustAllCerts, new SecureRandom());
+ System.out.println("WARNING: SSL certificate verification is DISABLED. All certificates will be trusted.");
+ return factory;
+ }
}
diff --git a/pre-flight-check/src/test/java/org/zowe/apiml/PreFlightCheckTest.java b/pre-flight-check/src/test/java/org/zowe/apiml/PreFlightCheckTest.java
index a361a75819..762a5c4a5d 100644
--- a/pre-flight-check/src/test/java/org/zowe/apiml/PreFlightCheckTest.java
+++ b/pre-flight-check/src/test/java/org/zowe/apiml/PreFlightCheckTest.java
@@ -63,12 +63,29 @@ void invalidSchemeIsRejected() {
}
@Test
- void httpsWithoutTruststoreIsRejected() {
+ void invalidVerifyCertificatesIsRejected() {
+ String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "443", "--scheme", "https",
+ "--truststore", "some/path.p12", "--truststore-password", "pass",
+ "--verify-certificates", "INVALID"};
+ assertEquals(4, PreFlightCheck.mainWithExitCode(args));
+ assertTrue(errStream.toString().contains("--verify-certificates must be STRICT, NONSTRICT, or DISABLED"));
+ }
+
+ @Test
+ void httpsStrictWithoutTruststoreIsRejected() {
String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "443", "--scheme", "https"};
assertEquals(4, PreFlightCheck.mainWithExitCode(args));
assertTrue(errStream.toString().contains("--truststore is required"));
}
+ @Test
+ void httpsNonstrictWithoutTruststoreIsRejected() {
+ String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "443", "--scheme", "https",
+ "--verify-certificates", "NONSTRICT"};
+ assertEquals(4, PreFlightCheck.mainWithExitCode(args));
+ assertTrue(errStream.toString().contains("--truststore is required"));
+ }
+
@Test
void httpsWithoutTruststorePasswordIsRejected() {
String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "443", "--scheme", "https",
@@ -77,6 +94,17 @@ void httpsWithoutTruststorePasswordIsRejected() {
assertTrue(errStream.toString().contains("--truststore-password is required"));
}
+ @Test
+ void httpsDisabledDoesNotRequireTruststore() {
+ // DISABLED mode skips certificate verification entirely — no truststore needed
+ // Will fail to connect to a non-existent server, but should pass validation
+ String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "19999", "--scheme", "https",
+ "--verify-certificates", "DISABLED"};
+ int exitCode = PreFlightCheck.mainWithExitCode(args);
+ assertEquals(4, exitCode);
+ assertFalse(errStream.toString().contains("--truststore is required"));
+ }
+
@Test
void httpDoesNotRequireTruststore() {
// This will fail to connect but should not fail validation
From 33a4b1e2d9555e8df835bbf20baa1ea481cf21e2 Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Sat, 28 Mar 2026 01:19:21 +0530
Subject: [PATCH 03/28] config change for publishing SDK
Signed-off-by: hrishikesh-nalawade
---
gradle/publish.gradle | 1 +
1 file changed, 1 insertion(+)
diff --git a/gradle/publish.gradle b/gradle/publish.gradle
index 607f189068..8f481a156d 100644
--- a/gradle/publish.gradle
+++ b/gradle/publish.gradle
@@ -11,6 +11,7 @@ ext.javaLibraries = [
'apiml-security-common',
'apiml-tomcat-common',
'certificate-analyser',
+ 'pre-flight-check',
'common-service-core',
'security-service-client-spring',
'apiml-sample-extension',
From 41f8345ea6241a0731aaa587a1ce3509ab77aa89 Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Tue, 31 Mar 2026 23:33:19 +0530
Subject: [PATCH 04/28] adding Java Doc
Signed-off-by: hrishikesh-nalawade
---
.../java/org/zowe/apiml/HttpClientWrapper.java | 5 +++++
.../org/zowe/apiml/JwkEndpointChecker.java | 4 ++++
.../java/org/zowe/apiml/PreFlightCheck.java | 7 +++++++
.../org/zowe/apiml/PreFlightCheckConf.java | 5 +++++
.../org/zowe/apiml/PreFlightCheckConfig.java | 5 +++++
.../java/org/zowe/apiml/SSLContextFactory.java | 18 ++++++++++++++++++
.../src/main/java/org/zowe/apiml/Stores.java | 16 ++++++++++++++++
.../apiml/StoresNotInitializeException.java | 4 ++++
8 files changed, 64 insertions(+)
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java b/pre-flight-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
index df3fa0d5e5..490f94f194 100644
--- a/pre-flight-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
@@ -18,6 +18,11 @@
import java.net.URL;
import java.util.Map;
+/**
+ * HTTP/HTTPS client wrapping {@link java.net.HttpURLConnection}.
+ * Supports custom {@link SSLContext} and {@link HostnameVerifier} for
+ * STRICT, NONSTRICT, and DISABLED certificate verification modes.
+ */
@SuppressWarnings("squid:S106")
public class HttpClientWrapper {
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java b/pre-flight-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
index c9301b2084..fcd68d119d 100644
--- a/pre-flight-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
@@ -17,6 +17,10 @@
import java.util.HashMap;
import java.util.Map;
+/**
+ * Checks z/OSMF JWK endpoint availability at {@code /jwt/ibm/api/zOSMFBuilder/jwk}.
+ * Interprets the HTTP response code to determine if the endpoint is functional
+ */
@SuppressWarnings("squid:S106")
public class JwkEndpointChecker {
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheck.java b/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheck.java
index 034860b9f1..b968d53a4f 100644
--- a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheck.java
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheck.java
@@ -14,6 +14,13 @@
import javax.net.ssl.HostnameVerifier;
+/**
+ * Entry point and orchestrator for the pre-flight check tool.
+ * Parses CLI arguments, validates configuration, builds the appropriate
+ * SSL context and HTTP client, then delegates to {@link JwkEndpointChecker}.
+ *
+ * Exit codes: 0 = success, 4 = failure/error, 8 = help displayed.
+ */
@SuppressWarnings("squid:S106")
public class PreFlightCheck {
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConf.java b/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConf.java
index 106c4c664c..5d3284f5c4 100644
--- a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConf.java
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConf.java
@@ -13,6 +13,11 @@
import picocli.CommandLine;
import picocli.CommandLine.Option;
+/**
+ * CLI argument parser backed by picocli.
+ * Maps command-line flags (e.g. {@code --zosmf-host}, {@code --verify-certificates})
+ * to configuration properties exposed via {@link PreFlightCheckConfig}.
+ */
@CommandLine.Command(
name = "pre-flight-check",
version = {
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConfig.java b/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConfig.java
index 0f0bb1e215..d42bbaaedb 100644
--- a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConfig.java
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConfig.java
@@ -10,6 +10,11 @@
package org.zowe.apiml;
+/**
+ * Configuration contract for the pre-flight check tool.
+ * Exposes all user-supplied settings such as z/OSMF host, port, scheme,
+ * keystore/truststore paths, and certificate verification mode.
+ */
public interface PreFlightCheckConfig {
String getZosmfHost();
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/SSLContextFactory.java b/pre-flight-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
index 480d6f03e8..8e4f8cedaf 100644
--- a/pre-flight-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
@@ -20,6 +20,13 @@
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+/**
+ * Builds a TLSv1.2 {@link SSLContext} in two modes:
+ *
+ * - {@link #initSSLContext(Stores)} — normal mode using real truststore/keystore
+ * - {@link #initTrustAllSSLContext()} — trust-all mode for DISABLED verification
+ *
+ */
@SuppressWarnings("squid:S106")
public class SSLContextFactory {
@@ -34,6 +41,12 @@ public SSLContext getSslContext() {
return sslContext;
}
+ /**
+ * Creates an SSLContext using the provided keystore and truststore.
+ *
+ * @param stores loaded keystore/truststore pair
+ * @return factory holding the initialized SSLContext
+ */
public static SSLContextFactory initSSLContext(Stores stores) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException, CertificateException, IOException {
SSLContextFactory factory = new SSLContextFactory(stores);
@@ -54,6 +67,11 @@ public static SSLContextFactory initSSLContext(Stores stores) throws NoSuchAlgor
return factory;
}
+ /**
+ * Creates an SSLContext that trusts all certificates. Use only when verification is DISABLED.
+ *
+ * @return factory holding the trust-all SSLContext
+ */
public static SSLContextFactory initTrustAllSSLContext() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, CertificateException, IOException, UnrecoverableKeyException {
SSLContextFactory factory = new SSLContextFactory(null);
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/Stores.java b/pre-flight-check/src/main/java/org/zowe/apiml/Stores.java
index 04041403fb..53cc962b27 100644
--- a/pre-flight-check/src/main/java/org/zowe/apiml/Stores.java
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/Stores.java
@@ -23,6 +23,10 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+/**
+ * Loads Java {@link java.security.KeyStore} instances from the filesystem
+ * or z/OS SAF keyrings. Supports PKCS12, JKS, and {@code safkeyring://} URIs.
+ */
@SuppressWarnings("squid:S106")
public class Stores {
@@ -37,12 +41,24 @@ public Stores(PreFlightCheckConfig conf) {
init();
}
+ /**
+ * Checks whether the given path is a SAF keyring URI.
+ *
+ * @param input store path to check
+ * @return {@code true} if the path matches the keyring pattern
+ */
public static boolean isKeyring(String input) {
if (input == null) return false;
Matcher matcher = KEYRING_PATTERN.matcher(input);
return matcher.matches();
}
+ /**
+ * Normalizes a keyring URI to the canonical {@code safkeyring://userId/keyRing} format.
+ *
+ * @param input raw keyring URI
+ * @return normalized URI, or the original input if not a keyring
+ */
public static String formatKeyringUrl(String input) {
if (input == null) return null;
Matcher matcher = KEYRING_PATTERN.matcher(input);
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/StoresNotInitializeException.java b/pre-flight-check/src/main/java/org/zowe/apiml/StoresNotInitializeException.java
index e5c77f773c..c6b5231cf2 100644
--- a/pre-flight-check/src/main/java/org/zowe/apiml/StoresNotInitializeException.java
+++ b/pre-flight-check/src/main/java/org/zowe/apiml/StoresNotInitializeException.java
@@ -10,6 +10,10 @@
package org.zowe.apiml;
+/**
+ * Thrown when keystore or truststore initialization fails
+ * (e.g. missing file, wrong password, invalid keyring format).
+ */
public class StoresNotInitializeException extends RuntimeException {
public StoresNotInitializeException(String message) {
From 138c47defea1c84856a43fcf2fa3ae7c82c70750 Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Wed, 1 Apr 2026 17:29:25 +0530
Subject: [PATCH 05/28] renaming pre-flight-check to zosmf-jwt-check
Signed-off-by: hrishikesh-nalawade
---
build.gradle | 2 +-
gradle/publish.gradle | 2 +-
settings.gradle | 2 +-
.../README.md | 58 +++++++------------
.../build.gradle | 2 +-
.../org/zowe/apiml/HttpClientWrapper.java | 0
.../org/zowe/apiml/JwkEndpointChecker.java | 16 +++--
.../org/zowe/apiml/SSLContextFactory.java | 0
.../src/main/java/org/zowe/apiml/Stores.java | 6 +-
.../apiml/StoresNotInitializeException.java | 0
.../java/org/zowe/apiml/ZosmfJwtCheck.java | 10 ++--
.../org/zowe/apiml/ZosmfJwtCheckConf.java | 10 ++--
.../org/zowe/apiml/ZosmfJwtCheckConfig.java | 4 +-
.../zowe/apiml/JwkEndpointCheckerTest.java | 6 +-
.../org/zowe/apiml/ZosmfJwtCheckTest.java | 22 +++----
15 files changed, 66 insertions(+), 74 deletions(-)
rename {pre-flight-check => zosmf-jwt-check}/README.md (81%)
rename {pre-flight-check => zosmf-jwt-check}/build.gradle (89%)
rename {pre-flight-check => zosmf-jwt-check}/src/main/java/org/zowe/apiml/HttpClientWrapper.java (100%)
rename {pre-flight-check => zosmf-jwt-check}/src/main/java/org/zowe/apiml/JwkEndpointChecker.java (81%)
rename {pre-flight-check => zosmf-jwt-check}/src/main/java/org/zowe/apiml/SSLContextFactory.java (100%)
rename {pre-flight-check => zosmf-jwt-check}/src/main/java/org/zowe/apiml/Stores.java (97%)
rename {pre-flight-check => zosmf-jwt-check}/src/main/java/org/zowe/apiml/StoresNotInitializeException.java (100%)
rename pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheck.java => zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java (93%)
rename pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConf.java => zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConf.java (91%)
rename pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConfig.java => zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConfig.java (89%)
rename {pre-flight-check => zosmf-jwt-check}/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java (96%)
rename pre-flight-check/src/test/java/org/zowe/apiml/PreFlightCheckTest.java => zosmf-jwt-check/src/test/java/org/zowe/apiml/ZosmfJwtCheckTest.java (85%)
diff --git a/build.gradle b/build.gradle
index c88fe4b4fe..9d8ed86a6e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -183,7 +183,7 @@ ext.javaLibraries = [
'apiml-security-common',
'apiml-tomcat-common',
'certificate-analyser',
- 'pre-flight-check',
+ 'zosmf-jwt-check',
'common-service-core',
'security-service-client-spring',
'apiml-sample-extension',
diff --git a/gradle/publish.gradle b/gradle/publish.gradle
index 8f481a156d..7a6982ce50 100644
--- a/gradle/publish.gradle
+++ b/gradle/publish.gradle
@@ -11,7 +11,7 @@ ext.javaLibraries = [
'apiml-security-common',
'apiml-tomcat-common',
'certificate-analyser',
- 'pre-flight-check',
+ 'zosmf-jwt-check',
'common-service-core',
'security-service-client-spring',
'apiml-sample-extension',
diff --git a/settings.gradle b/settings.gradle
index 8c4a32051f..aeb34a82e7 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -54,7 +54,7 @@ include 'onboarding-enabler-python'
include 'zaas-client'
include 'mock-services'
include 'certificate-analyser'
-include 'pre-flight-check'
+include 'zosmf-jwt-check'
include 'apiml-tomcat-common'
include 'apiml-sample-extension'
include 'apiml-sample-extension-package'
diff --git a/pre-flight-check/README.md b/zosmf-jwt-check/README.md
similarity index 81%
rename from pre-flight-check/README.md
rename to zosmf-jwt-check/README.md
index b84b00b87c..be134af7ed 100644
--- a/pre-flight-check/README.md
+++ b/zosmf-jwt-check/README.md
@@ -1,10 +1,9 @@
-# Pre-Flight Check Tool
+# z/OSMF JWT Check Tool
-A Java utility that verifies connectivity to the z/OSMF JWK endpoint **before/after** starting the Zowe API Mediation Layer. This tool helps diagnose configuration issues early such as incorrect hostnames, unreachable ports, missing certificates, or misconfigured z/OSMF by performing a lightweight HTTP(S) call to the z/OSMF JWK endpoint at `/jwt/ibm/api/zOSMFBuilder/jwk`.
+A Java utility that verifies connectivity to the z/OSMF JWK endpoint. This tool helps diagnose configuration issues early such as incorrect hostnames, unreachable ports, missing certificates, or misconfigured z/OSMF by performing a lightweight HTTP(S) call to the z/OSMF JWK endpoint at `/jwt/ibm/api/zOSMFBuilder/jwk`.
## Table of Contents
-- [Overview](#overview)
- [Prerequisites](#prerequisites)
- [Building](#building)
- [Usage](#usage)
@@ -22,19 +21,6 @@ A Java utility that verifies connectivity to the z/OSMF JWK endpoint **before/af
- [Troubleshooting](#troubleshooting)
---
-
-## Overview
-
-When Zowe API ML starts, it attempts to reach z/OSMF to obtain public keys for JWT token validation. If z/OSMF is unreachable or misconfigured, the startup fails with errors that can be difficult to diagnose. This pre-flight check tool isolates that connectivity test into a simple, standalone JAR that can be run before Zowe startup.
-
-**What it checks:**
-
-- TCP connectivity to the z/OSMF host and port
-- SSL/TLS handshake (when using HTTPS)
-- Certificate trust chain validation (STRICT/NONSTRICT modes)
-- Hostname verification (STRICT mode)
-- HTTP response from the JWK endpoint (`/jwt/ibm/api/zOSMFBuilder/jwk`)
-
## Prerequisites
- **Java 17 or higher** (Java 17, 21, or any later version)
@@ -46,33 +32,33 @@ When Zowe API ML starts, it attempts to reach z/OSMF to obtain public keys for J
From the root of the `api-layer` repository:
```bash
-./gradlew :pre-flight-check:build
+./gradlew :zosmf-jwt-check:build
```
On Windows:
```powershell
-.\gradlew :pre-flight-check:build
+.\gradlew :zosmf-jwt-check:build
```
The fat JAR (with all dependencies bundled) will be generated at:
```
-pre-flight-check/build/libs/pre-flight-check-.jar
+zosmf-jwt-check/build/libs/zosmf-jwt-check-.jar
```
-For example: `pre-flight-check/build/libs/pre-flight-check-3.5.12-SNAPSHOT.jar`
+For example: `zosmf-jwt-check/build/libs/zosmf-jwt-check-3.5.12-SNAPSHOT.jar`
## Usage
```bash
-java -jar pre-flight-check-.jar --zosmf-host --zosmf-port [options]
+java -jar zosmf-jwt-check-.jar --zosmf-host --zosmf-port [options]
```
-**Minimal example (DISABLED mode — quickest way to test):**
+**Minimal example (DISABLED mode, quickest way to test):**
```bash
-java -jar pre-flight-check-.jar \
+java -jar zosmf-jwt-check-.jar \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
--verify-certificates DISABLED
@@ -81,7 +67,7 @@ java -jar pre-flight-check-.jar \
**Full example (STRICT mode with truststore):**
```bash
-java -jar pre-flight-check-.jar \
+java -jar zosmf-jwt-check-.jar \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
--truststore /path/to/truststore.p12 \
@@ -91,7 +77,7 @@ java -jar pre-flight-check-.jar \
**Display help:**
```bash
-java -jar pre-flight-check-.jar --help
+java -jar zosmf-jwt-check-.jar --help
```
## CLI Flags Reference
@@ -206,7 +192,7 @@ Below are step-by-step commands for testing all modes. Replace `` with
The fastest way to verify basic TCP + HTTP connectivity:
```bash
-java -jar pre-flight-check/build/libs/pre-flight-check-.jar \
+java -jar zosmf-jwt-check/build/libs/zosmf-jwt-check-.jar \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
--verify-certificates DISABLED
@@ -225,7 +211,7 @@ SUCCESS: z/OSMF JWK endpoint exists (returned 401 Unauthorized — expected with
Requires a truststore containing the z/OSMF server's CA certificate (see [Creating a Truststore](#creating-a-truststore)):
```bash
-java -jar pre-flight-check/build/libs/pre-flight-check-.jar \
+java -jar zosmf-jwt-check/build/libs/zosmf-jwt-check-.jar \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
--truststore /path/to/zosmf-truststore.p12 \
@@ -252,7 +238,7 @@ Details: PKIX path building failed: ...unable to find valid certification path t
Useful when connecting via IP address but the certificate has a DNS name:
```bash
-java -jar pre-flight-check/build/libs/pre-flight-check-.jar \
+java -jar zosmf-jwt-check/build/libs/zosmf-jwt-check-.jar \
--zosmf-host 10.0.0.50 \
--zosmf-port 11443 \
--truststore /path/to/zosmf-truststore.p12 \
@@ -273,7 +259,7 @@ SUCCESS: z/OSMF JWK endpoint exists (returned 401 Unauthorized — expected with
For z/OSMF instances running on plain HTTP (uncommon):
```bash
-java -jar pre-flight-check/build/libs/pre-flight-check-.jar \
+java -jar zosmf-jwt-check/build/libs/zosmf-jwt-check-.jar \
--zosmf-host myzosmf.example.com \
--zosmf-port 80 \
--scheme http
@@ -285,15 +271,15 @@ java -jar pre-flight-check/build/libs/pre-flight-check-.jar \
```bash
# No arguments at all
-java -jar pre-flight-check-.jar
+java -jar zosmf-jwt-check-.jar
# Output: Missing required options: '--zosmf-host=', '--zosmf-port='
# Missing truststore in STRICT mode
-java -jar pre-flight-check-.jar --zosmf-host myhost --zosmf-port 443
+java -jar zosmf-jwt-check-.jar --zosmf-host myhost --zosmf-port 443
# Output: ERROR: --truststore is required when --scheme=https and verification is not DISABLED.
# Missing truststore password
-java -jar pre-flight-check-.jar --zosmf-host myhost --zosmf-port 443 --truststore my.p12
+java -jar zosmf-jwt-check-.jar --zosmf-host myhost --zosmf-port 443 --truststore my.p12
# Output: ERROR: --truststore-password is required when --scheme=https and verification is not DISABLED.
```
@@ -301,18 +287,18 @@ java -jar pre-flight-check-.jar --zosmf-host myhost --zosmf-port 443 --
```bash
# Invalid scheme
-java -jar pre-flight-check-.jar --zosmf-host myhost --zosmf-port 443 --scheme ftp
+java -jar zosmf-jwt-check-.jar --zosmf-host myhost --zosmf-port 443 --scheme ftp
# Output: ERROR: --scheme must be 'http' or 'https', got: ftp
# Invalid verify mode
-java -jar pre-flight-check-.jar --zosmf-host myhost --zosmf-port 443 --verify-certificates INVALID
+java -jar zosmf-jwt-check-.jar --zosmf-host myhost --zosmf-port 443 --verify-certificates INVALID
# Output: ERROR: --verify-certificates must be STRICT, NONSTRICT, or DISABLED, got: INVALID
```
**Unreachable host:**
```bash
-java -jar pre-flight-check-.jar --zosmf-host nonexistent.host --zosmf-port 443 --verify-certificates DISABLED
+java -jar zosmf-jwt-check-.jar --zosmf-host nonexistent.host --zosmf-port 443 --verify-certificates DISABLED
# Output: FAILURE: Cannot connect to nonexistent.host:443.
```
@@ -322,7 +308,7 @@ On z/OS, if you are using SAF keyrings instead of file-based keystores/truststor
```bash
java -Djava.protocol.handler.pkgs=com.ibm.crypto.provider \
- -jar pre-flight-check-.jar \
+ -jar zosmf-jwt-check-.jar \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
--truststore safkeyring://IZUSVR/ZoweKeyring \
diff --git a/pre-flight-check/build.gradle b/zosmf-jwt-check/build.gradle
similarity index 89%
rename from pre-flight-check/build.gradle
rename to zosmf-jwt-check/build.gradle
index ca98a4c3d4..e95188ccdf 100644
--- a/pre-flight-check/build.gradle
+++ b/zosmf-jwt-check/build.gradle
@@ -17,7 +17,7 @@ compileJava {
jar {
manifest {
attributes(
- 'Main-Class': 'org.zowe.apiml.PreFlightCheck'
+ 'Main-Class': 'org.zowe.apiml.ZosmfJwtCheck'
)
}
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
similarity index 100%
rename from pre-flight-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
rename to zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
similarity index 81%
rename from pre-flight-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
rename to zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
index fcd68d119d..648099e2e9 100644
--- a/pre-flight-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
@@ -13,6 +13,7 @@
import javax.net.ssl.SSLHandshakeException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
@@ -28,9 +29,9 @@ public class JwkEndpointChecker {
private static final String ZOSMF_CSRF_HEADER = "X-CSRF-ZOSMF-HEADER";
private final HttpClientWrapper httpClient;
- private final PreFlightCheckConfig conf;
+ private final ZosmfJwtCheckConfig conf;
- public JwkEndpointChecker(HttpClientWrapper httpClient, PreFlightCheckConfig conf) {
+ public JwkEndpointChecker(HttpClientWrapper httpClient, ZosmfJwtCheckConfig conf) {
this.httpClient = httpClient;
this.conf = conf;
}
@@ -59,10 +60,15 @@ public boolean check() {
return false;
} catch (SocketTimeoutException e) {
System.err.println("FAILURE: Connection timed out to " + conf.getZosmfHost() + ":" + conf.getZosmfPort() + ".");
- System.err.println("Details: " + e.getMessage());
+ System.err.println("This is commonly caused by an incorrect host/port or a firewall blocking the connection.");
+ System.err.println("Verify the z/OSMF host and port are correct and that no firewall is blocking access.");
+ return false;
+ } catch (UnknownHostException e) {
+ System.err.println("FAILURE: Error when calling " + urlString + " verify hostname and port.");
+ System.err.println("The host '" + conf.getZosmfHost() + "' could not be resolved.");
return false;
} catch (Exception e) {
- System.err.println("FAILURE: Unexpected error when calling " + urlString + ".");
+ System.err.println("FAILURE: Error when calling " + urlString + " verify hostname and port.");
System.err.println("Details: " + e.getMessage());
return false;
}
@@ -81,7 +87,7 @@ private boolean evaluateResponseCode(int responseCode, String urlString) {
if (responseCode == 404) {
System.err.println("FAILURE: z/OSMF JWK endpoint not found. HTTP 404");
- System.err.println("Try configuring the jwtAutoConfiguration to LTPA");
+ System.err.println("JWT support not found, may not be configured. LTPA may be used as an alternative");
return false;
}
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/SSLContextFactory.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
similarity index 100%
rename from pre-flight-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
rename to zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/Stores.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
similarity index 97%
rename from pre-flight-check/src/main/java/org/zowe/apiml/Stores.java
rename to zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
index 53cc962b27..ef7b527e67 100644
--- a/pre-flight-check/src/main/java/org/zowe/apiml/Stores.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
@@ -34,9 +34,9 @@ public class Stores {
private KeyStore keyStore;
private KeyStore trustStore;
- private final PreFlightCheckConfig conf;
+ private final ZosmfJwtCheckConfig conf;
- public Stores(PreFlightCheckConfig conf) {
+ public Stores(ZosmfJwtCheckConfig conf) {
this.conf = conf;
init();
}
@@ -130,7 +130,7 @@ public KeyStore getTrustStore() {
return trustStore;
}
- public PreFlightCheckConfig getConf() {
+ public ZosmfJwtCheckConfig getConf() {
return conf;
}
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/StoresNotInitializeException.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/StoresNotInitializeException.java
similarity index 100%
rename from pre-flight-check/src/main/java/org/zowe/apiml/StoresNotInitializeException.java
rename to zosmf-jwt-check/src/main/java/org/zowe/apiml/StoresNotInitializeException.java
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheck.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
similarity index 93%
rename from pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheck.java
rename to zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
index b968d53a4f..dbadbd0ca7 100644
--- a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheck.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
@@ -15,14 +15,14 @@
import javax.net.ssl.HostnameVerifier;
/**
- * Entry point and orchestrator for the pre-flight check tool.
+ * Entry point and orchestrator for the z/OSMF JWT check tool.
* Parses CLI arguments, validates configuration, builds the appropriate
* SSL context and HTTP client, then delegates to {@link JwkEndpointChecker}.
*
* Exit codes: 0 = success, 4 = failure/error, 8 = help displayed.
*/
@SuppressWarnings("squid:S106")
-public class PreFlightCheck {
+public class ZosmfJwtCheck {
static final String VERIFY_STRICT = "STRICT";
static final String VERIFY_NONSTRICT = "NONSTRICT";
@@ -30,13 +30,13 @@ public class PreFlightCheck {
public static int mainWithExitCode(String[] args) {
try {
- PreFlightCheckConf conf = new PreFlightCheckConf();
+ ZosmfJwtCheckConf conf = new ZosmfJwtCheckConf();
CommandLine cmd = new CommandLine(conf);
cmd.parseArgs(args);
if (conf.isHelpRequested()) {
cmd.printVersionHelp(System.out);
- CommandLine.usage(new PreFlightCheckConf(), System.out);
+ CommandLine.usage(new ZosmfJwtCheckConf(), System.out);
return 8;
}
@@ -76,7 +76,7 @@ public static int mainWithExitCode(String[] args) {
}
}
- static void validateConfig(PreFlightCheckConf conf) {
+ static void validateConfig(ZosmfJwtCheckConf conf) {
String scheme = conf.getScheme();
if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) {
throw new IllegalArgumentException("--scheme must be 'http' or 'https', got: " + scheme);
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConf.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConf.java
similarity index 91%
rename from pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConf.java
rename to zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConf.java
index 5d3284f5c4..9fc3cb1017 100644
--- a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConf.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConf.java
@@ -16,18 +16,18 @@
/**
* CLI argument parser backed by picocli.
* Maps command-line flags (e.g. {@code --zosmf-host}, {@code --verify-certificates})
- * to configuration properties exposed via {@link PreFlightCheckConfig}.
+ * to configuration properties exposed via {@link ZosmfJwtCheckConfig}.
*/
@CommandLine.Command(
- name = "pre-flight-check",
+ name = "zosmf-jwt-check",
version = {
- "Pre-Flight Check 1.0",
+ "z/OSMF JWT Check 1.0",
"JVM: ${java.version} (${java.vendor} ${java.vm.name} ${java.vm.version})",
"OS: ${os.name} ${os.version} ${os.arch}"
},
- description = "Performs a pre-flight connectivity check against the z/OSMF JWK endpoint."
+ description = "Checks connectivity to the z/OSMF JWK endpoint."
)
-public class PreFlightCheckConf implements PreFlightCheckConfig {
+public class ZosmfJwtCheckConf implements ZosmfJwtCheckConfig {
@Option(names = {"--zosmf-host"}, required = true, description = "Hostname or IP of the z/OSMF server")
private String zosmfHost;
diff --git a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConfig.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConfig.java
similarity index 89%
rename from pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConfig.java
rename to zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConfig.java
index d42bbaaedb..f94d10fc40 100644
--- a/pre-flight-check/src/main/java/org/zowe/apiml/PreFlightCheckConfig.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConfig.java
@@ -11,11 +11,11 @@
package org.zowe.apiml;
/**
- * Configuration contract for the pre-flight check tool.
+ * Configuration contract for the z/OSMF JWT check tool.
* Exposes all user-supplied settings such as z/OSMF host, port, scheme,
* keystore/truststore paths, and certificate verification mode.
*/
-public interface PreFlightCheckConfig {
+public interface ZosmfJwtCheckConfig {
String getZosmfHost();
diff --git a/pre-flight-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java b/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
similarity index 96%
rename from pre-flight-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
rename to zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
index 642f4d507c..09370fe7d0 100644
--- a/pre-flight-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
+++ b/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
@@ -36,7 +36,7 @@ class JwkEndpointCheckerTest {
private final PrintStream originalErr = System.err;
private HttpClientWrapper mockClient;
- private PreFlightCheckConfig mockConf;
+ private ZosmfJwtCheckConfig mockConf;
@BeforeEach
void setUp() {
@@ -44,7 +44,7 @@ void setUp() {
System.setErr(new PrintStream(errStream));
mockClient = mock(HttpClientWrapper.class);
- mockConf = mock(PreFlightCheckConfig.class);
+ mockConf = mock(ZosmfJwtCheckConfig.class);
when(mockConf.getScheme()).thenReturn("https");
when(mockConf.getZosmfHost()).thenReturn("zosmf.example.com");
when(mockConf.getZosmfPort()).thenReturn(443);
@@ -142,7 +142,7 @@ void unexpectedExceptionIsHandled() throws IOException {
when(mockClient.executeCall(any(), anyMap())).thenThrow(new IOException("unexpected"));
JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
assertFalse(checker.check());
- assertTrue(errStream.toString().contains("Unexpected error"));
+ assertTrue(errStream.toString().contains("verify hostname and port"));
}
}
}
diff --git a/pre-flight-check/src/test/java/org/zowe/apiml/PreFlightCheckTest.java b/zosmf-jwt-check/src/test/java/org/zowe/apiml/ZosmfJwtCheckTest.java
similarity index 85%
rename from pre-flight-check/src/test/java/org/zowe/apiml/PreFlightCheckTest.java
rename to zosmf-jwt-check/src/test/java/org/zowe/apiml/ZosmfJwtCheckTest.java
index 762a5c4a5d..c4a96eb1b9 100644
--- a/pre-flight-check/src/test/java/org/zowe/apiml/PreFlightCheckTest.java
+++ b/zosmf-jwt-check/src/test/java/org/zowe/apiml/ZosmfJwtCheckTest.java
@@ -20,7 +20,7 @@
import static org.junit.jupiter.api.Assertions.*;
-class PreFlightCheckTest {
+class ZosmfJwtCheckTest {
private final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
private final ByteArrayOutputStream errStream = new ByteArrayOutputStream();
@@ -42,14 +42,14 @@ void restoreStreams() {
@Test
void helpFlagReturnsExitCode8() {
String[] args = {"--help"};
- assertEquals(8, PreFlightCheck.mainWithExitCode(args));
- assertTrue(outStream.toString().contains("Pre-Flight Check"));
+ assertEquals(8, ZosmfJwtCheck.mainWithExitCode(args));
+ assertTrue(outStream.toString().contains("z/OSMF JWT Check"));
}
@Test
void missingRequiredArgsReturnsExitCode4() {
String[] args = {};
- assertEquals(4, PreFlightCheck.mainWithExitCode(args));
+ assertEquals(4, ZosmfJwtCheck.mainWithExitCode(args));
}
@Nested
@@ -58,7 +58,7 @@ class ValidationTests {
@Test
void invalidSchemeIsRejected() {
String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "443", "--scheme", "ftp"};
- assertEquals(4, PreFlightCheck.mainWithExitCode(args));
+ assertEquals(4, ZosmfJwtCheck.mainWithExitCode(args));
assertTrue(errStream.toString().contains("--scheme must be 'http' or 'https'"));
}
@@ -67,14 +67,14 @@ void invalidVerifyCertificatesIsRejected() {
String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "443", "--scheme", "https",
"--truststore", "some/path.p12", "--truststore-password", "pass",
"--verify-certificates", "INVALID"};
- assertEquals(4, PreFlightCheck.mainWithExitCode(args));
+ assertEquals(4, ZosmfJwtCheck.mainWithExitCode(args));
assertTrue(errStream.toString().contains("--verify-certificates must be STRICT, NONSTRICT, or DISABLED"));
}
@Test
void httpsStrictWithoutTruststoreIsRejected() {
String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "443", "--scheme", "https"};
- assertEquals(4, PreFlightCheck.mainWithExitCode(args));
+ assertEquals(4, ZosmfJwtCheck.mainWithExitCode(args));
assertTrue(errStream.toString().contains("--truststore is required"));
}
@@ -82,7 +82,7 @@ void httpsStrictWithoutTruststoreIsRejected() {
void httpsNonstrictWithoutTruststoreIsRejected() {
String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "443", "--scheme", "https",
"--verify-certificates", "NONSTRICT"};
- assertEquals(4, PreFlightCheck.mainWithExitCode(args));
+ assertEquals(4, ZosmfJwtCheck.mainWithExitCode(args));
assertTrue(errStream.toString().contains("--truststore is required"));
}
@@ -90,7 +90,7 @@ void httpsNonstrictWithoutTruststoreIsRejected() {
void httpsWithoutTruststorePasswordIsRejected() {
String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "443", "--scheme", "https",
"--truststore", "some/path.p12"};
- assertEquals(4, PreFlightCheck.mainWithExitCode(args));
+ assertEquals(4, ZosmfJwtCheck.mainWithExitCode(args));
assertTrue(errStream.toString().contains("--truststore-password is required"));
}
@@ -100,7 +100,7 @@ void httpsDisabledDoesNotRequireTruststore() {
// Will fail to connect to a non-existent server, but should pass validation
String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "19999", "--scheme", "https",
"--verify-certificates", "DISABLED"};
- int exitCode = PreFlightCheck.mainWithExitCode(args);
+ int exitCode = ZosmfJwtCheck.mainWithExitCode(args);
assertEquals(4, exitCode);
assertFalse(errStream.toString().contains("--truststore is required"));
}
@@ -109,7 +109,7 @@ void httpsDisabledDoesNotRequireTruststore() {
void httpDoesNotRequireTruststore() {
// This will fail to connect but should not fail validation
String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "19999", "--scheme", "http"};
- int exitCode = PreFlightCheck.mainWithExitCode(args);
+ int exitCode = ZosmfJwtCheck.mainWithExitCode(args);
// Should be 4 (connection failure) not a validation error
assertEquals(4, exitCode);
assertFalse(errStream.toString().contains("--truststore is required"));
From 0b1f19624f9102d527e49307dba2ba650da96fc7 Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Wed, 1 Apr 2026 19:17:28 +0530
Subject: [PATCH 06/28] Added verbose mode (-v) support
Signed-off-by: hrishikesh-nalawade
---
.../org/zowe/apiml/HttpClientWrapper.java | 35 +++++++++++++++++--
.../org/zowe/apiml/JwkEndpointChecker.java | 9 +++--
.../org/zowe/apiml/ZosmfJwtCheckConf.java | 8 +++++
.../org/zowe/apiml/ZosmfJwtCheckConfig.java | 2 ++
.../zowe/apiml/JwkEndpointCheckerTest.java | 11 +++---
5 files changed, 55 insertions(+), 10 deletions(-)
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
index 490f94f194..8faca04157 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
@@ -13,10 +13,15 @@
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
+import java.io.BufferedReader;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.util.Map;
+import java.util.stream.Collectors;
/**
* HTTP/HTTPS client wrapping {@link java.net.HttpURLConnection}.
@@ -29,6 +34,19 @@ public class HttpClientWrapper {
private static final int CONNECT_TIMEOUT = 5000;
private static final int READ_TIMEOUT = 5000;
+ public static class Response {
+ private final int statusCode;
+ private final String body;
+
+ public Response(int statusCode, String body) {
+ this.statusCode = statusCode;
+ this.body = body;
+ }
+
+ public int getStatusCode() { return statusCode; }
+ public String getBody() { return body; }
+ }
+
private final SSLContext sslContext;
private final boolean useHttps;
private final HostnameVerifier hostnameVerifier;
@@ -45,7 +63,7 @@ public HttpClientWrapper() {
this.hostnameVerifier = null;
}
- public int executeCall(URL url, Map headers) throws IOException {
+ public Response executeCall(URL url, Map headers) throws IOException {
HttpURLConnection con;
if (useHttps) {
HttpsURLConnection httpsCon = (HttpsURLConnection) url.openConnection();
@@ -69,9 +87,22 @@ public int executeCall(URL url, Map headers) throws IOException
}
try {
- return con.getResponseCode();
+ int responseCode = con.getResponseCode();
+ String body = readBody(con);
+ return new Response(responseCode, body);
} finally {
con.disconnect();
}
}
+
+ private String readBody(HttpURLConnection con) {
+ try {
+ InputStream is = con.getErrorStream() != null ? con.getErrorStream() : con.getInputStream();
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
+ return reader.lines().collect(Collectors.joining("\n"));
+ }
+ } catch (Exception e) {
+ return null;
+ }
+ }
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
index 648099e2e9..3eaeea9e11 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
@@ -46,8 +46,11 @@ public boolean check() {
URL url = new URL(urlString);
System.out.println("Checking z/OSMF JWK endpoint: " + urlString);
- int responseCode = httpClient.executeCall(url, headers);
- return evaluateResponseCode(responseCode, urlString);
+ HttpClientWrapper.Response response = httpClient.executeCall(url, headers);
+ if (conf.isVerbose() && response.getBody() != null) {
+ System.out.println("Response body:\n" + response.getBody());
+ }
+ return evaluateResponseCode(response.getStatusCode(), urlString);
} catch (SSLHandshakeException e) {
System.err.println("FAILURE: SSL handshake failed when connecting to " + urlString + ".");
System.err.println("Verify that the truststore contains the z/OSMF server certificate.");
@@ -81,7 +84,7 @@ private boolean evaluateResponseCode(int responseCode, String urlString) {
}
if (responseCode == 401) {
- System.out.println("SUCCESS: z/OSMF JWK endpoint exists (returned 401 Unauthorized — expected without credentials). HTTP 401");
+ System.out.println("SUCCESS: z/OSMF JWK endpoint exists (returned 401 Unauthorized, expected without credentials). HTTP 401");
return true;
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConf.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConf.java
index 9fc3cb1017..8296ce0572 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConf.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConf.java
@@ -59,6 +59,9 @@ public class ZosmfJwtCheckConf implements ZosmfJwtCheckConfig {
@Option(names = {"--verify-certificates"}, description = "Certificate verification mode: STRICT, NONSTRICT, or DISABLED (default: ${DEFAULT-VALUE})")
private String verifyCertificates = "STRICT";
+ @Option(names = {"-v", "--verbose"}, description = "Print the response body from the endpoint")
+ private boolean verbose = false;
+
@Option(names = {"-h", "--help"}, usageHelp = true, description = "Display a help message")
private boolean helpRequested = false;
@@ -112,6 +115,11 @@ public String getVerifyCertificates() {
return verifyCertificates;
}
+ @Override
+ public boolean isVerbose() {
+ return verbose;
+ }
+
@Override
public boolean isHelpRequested() {
return helpRequested;
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConfig.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConfig.java
index f94d10fc40..f52f7832e1 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConfig.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConfig.java
@@ -37,5 +37,7 @@ public interface ZosmfJwtCheckConfig {
String getVerifyCertificates();
+ boolean isVerbose();
+
boolean isHelpRequested();
}
diff --git a/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java b/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
index 09370fe7d0..8e3d813388 100644
--- a/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
+++ b/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
@@ -48,6 +48,7 @@ void setUp() {
when(mockConf.getScheme()).thenReturn("https");
when(mockConf.getZosmfHost()).thenReturn("zosmf.example.com");
when(mockConf.getZosmfPort()).thenReturn(443);
+ when(mockConf.isVerbose()).thenReturn(false);
}
@AfterEach
@@ -61,7 +62,7 @@ class SuccessResponses {
@Test
void response200IsSuccess() throws IOException {
- when(mockClient.executeCall(any(), anyMap())).thenReturn(200);
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(200, ""));
JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
assertTrue(checker.check());
assertTrue(outStream.toString().contains("SUCCESS"));
@@ -70,7 +71,7 @@ void response200IsSuccess() throws IOException {
@Test
void response401IsSuccess() throws IOException {
- when(mockClient.executeCall(any(), anyMap())).thenReturn(401);
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(401, ""));
JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
assertTrue(checker.check());
assertTrue(outStream.toString().contains("SUCCESS"));
@@ -83,7 +84,7 @@ class FailureResponses {
@Test
void response404IsFailure() throws IOException {
- when(mockClient.executeCall(any(), anyMap())).thenReturn(404);
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(404, ""));
JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
assertFalse(checker.check());
assertTrue(errStream.toString().contains("FAILURE"));
@@ -92,7 +93,7 @@ void response404IsFailure() throws IOException {
@Test
void response500IsFailure() throws IOException {
- when(mockClient.executeCall(any(), anyMap())).thenReturn(500);
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(500, ""));
JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
assertFalse(checker.check());
assertTrue(errStream.toString().contains("FAILURE"));
@@ -101,7 +102,7 @@ void response500IsFailure() throws IOException {
@Test
void response403IsFailure() throws IOException {
- when(mockClient.executeCall(any(), anyMap())).thenReturn(403);
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(403, ""));
JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
assertFalse(checker.check());
assertTrue(errStream.toString().contains("FAILURE"));
From 5b2d30c305b70f9cb0d589c497d4408fb367911f Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Wed, 1 Apr 2026 19:57:03 +0530
Subject: [PATCH 07/28] empty response body handling
Signed-off-by: hrishikesh-nalawade
---
.../org/zowe/apiml/JwkEndpointChecker.java | 37 ++++++++++++++++++-
.../zowe/apiml/JwkEndpointCheckerTest.java | 10 ++++-
2 files changed, 44 insertions(+), 3 deletions(-)
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
index 3eaeea9e11..06f5b0681a 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
@@ -17,6 +17,8 @@
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Checks z/OSMF JWK endpoint availability at {@code /jwt/ibm/api/zOSMFBuilder/jwk}.
@@ -27,6 +29,7 @@ public class JwkEndpointChecker {
static final String JWK_ENDPOINT_PATH = "/jwt/ibm/api/zOSMFBuilder/jwk";
private static final String ZOSMF_CSRF_HEADER = "X-CSRF-ZOSMF-HEADER";
+ private static final Pattern N_VALUE_PATTERN = Pattern.compile("\"n\"\\s*:\\s*\"([^\"]*)\"");
private final HttpClientWrapper httpClient;
private final ZosmfJwtCheckConfig conf;
@@ -50,7 +53,7 @@ public boolean check() {
if (conf.isVerbose() && response.getBody() != null) {
System.out.println("Response body:\n" + response.getBody());
}
- return evaluateResponseCode(response.getStatusCode(), urlString);
+ return evaluateResponseCode(response.getStatusCode(), response.getBody(), urlString);
} catch (SSLHandshakeException e) {
System.err.println("FAILURE: SSL handshake failed when connecting to " + urlString + ".");
System.err.println("Verify that the truststore contains the z/OSMF server certificate.");
@@ -77,8 +80,11 @@ public boolean check() {
}
}
- private boolean evaluateResponseCode(int responseCode, String urlString) {
+ boolean evaluateResponseCode(int responseCode, String body, String urlString) {
if (responseCode >= 200 && responseCode < 300) {
+ if (!validateJwkBody(body)) {
+ return false;
+ }
System.out.println("SUCCESS: z/OSMF JWK endpoint is reachable and responding. HTTP " + responseCode);
return true;
}
@@ -110,4 +116,31 @@ private boolean evaluateResponseCode(int responseCode, String urlString) {
System.err.println("URL: " + urlString);
return false;
}
+
+ boolean validateJwkBody(String body) {
+ if (body == null || body.isEmpty()) {
+ System.err.println("WARNING: z/OSMF JWK endpoint returned an empty response body.");
+ System.err.println("Response body: " + (body == null ? "" : ""));
+ return false;
+ }
+
+ Matcher matcher = N_VALUE_PATTERN.matcher(body);
+ if (!matcher.find()) {
+ System.err.println("WARNING: JWK response does not contain an RSA modulus (\"n\" key).");
+ System.err.println("The z/OSMF JWK endpoint may not be properly configured.");
+ System.err.println("Response body: " + body);
+ return false;
+ }
+
+ String nValue = matcher.group(1);
+ if (nValue == null || nValue.trim().isEmpty()) {
+ System.err.println("FAILURE: JWK response contains an empty RSA modulus (\"n\" key is empty).");
+ System.err.println("The z/OSMF server returned a key that cannot be used for JWT verification.");
+ System.err.println("Check z/OSMF JWT configuration and ensure the signing key is properly generated.");
+ System.err.println("Response body: " + body);
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java b/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
index 8e3d813388..7e30a847d8 100644
--- a/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
+++ b/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
@@ -62,7 +62,7 @@ class SuccessResponses {
@Test
void response200IsSuccess() throws IOException {
- when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(200, ""));
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(200, "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"n\":\"validModulusValue\"}]}"));
JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
assertTrue(checker.check());
assertTrue(outStream.toString().contains("SUCCESS"));
@@ -77,6 +77,14 @@ void response401IsSuccess() throws IOException {
assertTrue(outStream.toString().contains("SUCCESS"));
assertTrue(outStream.toString().contains("401"));
}
+
+ @Test
+ void response200WithEmptyModulusIsFailure() throws IOException {
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(200, "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"n\":\"\"}]}"));
+ JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
+ assertFalse(checker.check());
+ assertTrue(errStream.toString().contains("empty RSA modulus"));
+ }
}
@Nested
From 074a84f763fff9d49ac94cd92988ea5fc85e80ab Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Thu, 2 Apr 2026 16:41:28 +0530
Subject: [PATCH 08/28] updating --truststore to --truststore-file
Signed-off-by: hrishikesh-nalawade
---
zosmf-jwt-check/README.md | 16 ++++++++--------
.../main/java/org/zowe/apiml/ZosmfJwtCheck.java | 2 +-
.../java/org/zowe/apiml/ZosmfJwtCheckConf.java | 4 ++--
.../java/org/zowe/apiml/ZosmfJwtCheckTest.java | 12 ++++++------
4 files changed, 17 insertions(+), 17 deletions(-)
diff --git a/zosmf-jwt-check/README.md b/zosmf-jwt-check/README.md
index be134af7ed..7536e4aa71 100644
--- a/zosmf-jwt-check/README.md
+++ b/zosmf-jwt-check/README.md
@@ -70,7 +70,7 @@ java -jar zosmf-jwt-check-.jar \
java -jar zosmf-jwt-check-.jar \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
- --truststore /path/to/truststore.p12 \
+ --truststore-file /path/to/truststore.p12 \
--truststore-password changeit
```
@@ -98,7 +98,7 @@ These flags are required when `--scheme=https` (the default) and `--verify-certi
| Flag | Description | Error when missing |
|------|-------------|-------------------|
-| `--truststore` | Path to the truststore file containing the z/OSMF CA certificate | `ERROR: --truststore is required when --scheme=https and verification is not DISABLED.` |
+| `--truststore-file` | Path to the truststore file containing the z/OSMF CA certificate | `ERROR: --truststore-file is required when --scheme=https and verification is not DISABLED.` |
| `--truststore-password` | Password for the truststore. If specified without a value, you will be prompted interactively. | `ERROR: --truststore-password is required when --scheme=https and verification is not DISABLED.` |
### Optional Flags
@@ -108,7 +108,7 @@ These flags are required when `--scheme=https` (the default) and `--verify-certi
| `--scheme` | `https` | Protocol to use: `http` or `https` |
| `--verify-certificates` | `STRICT` | Certificate verification mode: `STRICT`, `NONSTRICT`, or `DISABLED` |
| `--truststore-type` | `PKCS12` | Format of the truststore file (e.g., `PKCS12`, `JKS`, `JCERACFKS`) |
-| `--keystore` | *(none)* | Path to keystore file (only needed for mutual TLS / client certificate authentication) |
+| `--keystore-file` | *(none)* | Path to keystore file (only needed for mutual TLS / client certificate authentication) |
| `--keystore-password` | *(none)* | Password for the keystore. If specified without a value, you will be prompted interactively. |
| `--keystore-type` | `PKCS12` | Format of the keystore file |
| `-h`, `--help` | | Display usage help and exit |
@@ -214,7 +214,7 @@ Requires a truststore containing the z/OSMF server's CA certificate (see [Creati
java -jar zosmf-jwt-check/build/libs/zosmf-jwt-check-.jar \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
- --truststore /path/to/zosmf-truststore.p12 \
+ --truststore-file /path/to/zosmf-truststore.p12 \
--truststore-password password
```
@@ -241,7 +241,7 @@ Useful when connecting via IP address but the certificate has a DNS name:
java -jar zosmf-jwt-check/build/libs/zosmf-jwt-check-.jar \
--zosmf-host 10.0.0.50 \
--zosmf-port 11443 \
- --truststore /path/to/zosmf-truststore.p12 \
+ --truststore-file /path/to/zosmf-truststore.p12 \
--truststore-password password \
--verify-certificates NONSTRICT
```
@@ -276,10 +276,10 @@ java -jar zosmf-jwt-check-.jar
# Missing truststore in STRICT mode
java -jar zosmf-jwt-check-.jar --zosmf-host myhost --zosmf-port 443
-# Output: ERROR: --truststore is required when --scheme=https and verification is not DISABLED.
+# Output: ERROR: --truststore-file is required when --scheme=https and verification is not DISABLED.
# Missing truststore password
-java -jar zosmf-jwt-check-.jar --zosmf-host myhost --zosmf-port 443 --truststore my.p12
+java -jar zosmf-jwt-check-.jar --zosmf-host myhost --zosmf-port 443 --truststore-file my.p12
# Output: ERROR: --truststore-password is required when --scheme=https and verification is not DISABLED.
```
@@ -311,7 +311,7 @@ java -Djava.protocol.handler.pkgs=com.ibm.crypto.provider \
-jar zosmf-jwt-check-.jar \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
- --truststore safkeyring://IZUSVR/ZoweKeyring \
+ --truststore-file safkeyring://IZUSVR/ZoweKeyring \
--truststore-password password \
--truststore-type JCERACFKS
```
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
index dbadbd0ca7..bb13cc57bc 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
@@ -89,7 +89,7 @@ static void validateConfig(ZosmfJwtCheckConf conf) {
if ("https".equalsIgnoreCase(scheme) && !VERIFY_DISABLED.equals(verifyMode)) {
if (conf.getTrustStore() == null) {
- throw new IllegalArgumentException("--truststore is required when --scheme=https and verification is not DISABLED. " +
+ throw new IllegalArgumentException("--truststore-file is required when --scheme=https and verification is not DISABLED. " +
"Provide the path to the truststore containing the z/OSMF server certificate.");
}
if (conf.getTrustStorePassword() == null) {
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConf.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConf.java
index 8296ce0572..9bb9096ecc 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConf.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheckConf.java
@@ -38,7 +38,7 @@ public class ZosmfJwtCheckConf implements ZosmfJwtCheckConfig {
@Option(names = {"--scheme"}, description = "http or https (default: ${DEFAULT-VALUE})")
private String scheme = "https";
- @Option(names = {"--keystore"}, description = "Path to the keystore file (for HTTPS mutual TLS)")
+ @Option(names = {"--keystore-file"}, description = "Path to the keystore file (for HTTPS mutual TLS)")
private String keyStore;
@Option(names = {"--keystore-password"}, arity = "0..1", interactive = true, description = "Password for the keystore")
@@ -47,7 +47,7 @@ public class ZosmfJwtCheckConf implements ZosmfJwtCheckConfig {
@Option(names = {"--keystore-type"}, description = "Type of keystore (default: ${DEFAULT-VALUE})")
private String keyStoreType = "PKCS12";
- @Option(names = {"--truststore"}, description = "Path to the truststore file (for HTTPS)")
+ @Option(names = {"--truststore-file"}, description = "Path to the truststore file (for HTTPS)")
private String trustStore;
@Option(names = {"--truststore-password"}, arity = "0..1", interactive = true, description = "Password for the truststore")
diff --git a/zosmf-jwt-check/src/test/java/org/zowe/apiml/ZosmfJwtCheckTest.java b/zosmf-jwt-check/src/test/java/org/zowe/apiml/ZosmfJwtCheckTest.java
index c4a96eb1b9..9da86cbcea 100644
--- a/zosmf-jwt-check/src/test/java/org/zowe/apiml/ZosmfJwtCheckTest.java
+++ b/zosmf-jwt-check/src/test/java/org/zowe/apiml/ZosmfJwtCheckTest.java
@@ -65,7 +65,7 @@ void invalidSchemeIsRejected() {
@Test
void invalidVerifyCertificatesIsRejected() {
String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "443", "--scheme", "https",
- "--truststore", "some/path.p12", "--truststore-password", "pass",
+ "--truststore-file", "some/path.p12", "--truststore-password", "pass",
"--verify-certificates", "INVALID"};
assertEquals(4, ZosmfJwtCheck.mainWithExitCode(args));
assertTrue(errStream.toString().contains("--verify-certificates must be STRICT, NONSTRICT, or DISABLED"));
@@ -75,7 +75,7 @@ void invalidVerifyCertificatesIsRejected() {
void httpsStrictWithoutTruststoreIsRejected() {
String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "443", "--scheme", "https"};
assertEquals(4, ZosmfJwtCheck.mainWithExitCode(args));
- assertTrue(errStream.toString().contains("--truststore is required"));
+ assertTrue(errStream.toString().contains("--truststore-file is required"));
}
@Test
@@ -83,13 +83,13 @@ void httpsNonstrictWithoutTruststoreIsRejected() {
String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "443", "--scheme", "https",
"--verify-certificates", "NONSTRICT"};
assertEquals(4, ZosmfJwtCheck.mainWithExitCode(args));
- assertTrue(errStream.toString().contains("--truststore is required"));
+ assertTrue(errStream.toString().contains("--truststore-file is required"));
}
@Test
void httpsWithoutTruststorePasswordIsRejected() {
String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "443", "--scheme", "https",
- "--truststore", "some/path.p12"};
+ "--truststore-file", "some/path.p12"};
assertEquals(4, ZosmfJwtCheck.mainWithExitCode(args));
assertTrue(errStream.toString().contains("--truststore-password is required"));
}
@@ -102,7 +102,7 @@ void httpsDisabledDoesNotRequireTruststore() {
"--verify-certificates", "DISABLED"};
int exitCode = ZosmfJwtCheck.mainWithExitCode(args);
assertEquals(4, exitCode);
- assertFalse(errStream.toString().contains("--truststore is required"));
+ assertFalse(errStream.toString().contains("--truststore-file is required"));
}
@Test
@@ -112,7 +112,7 @@ void httpDoesNotRequireTruststore() {
int exitCode = ZosmfJwtCheck.mainWithExitCode(args);
// Should be 4 (connection failure) not a validation error
assertEquals(4, exitCode);
- assertFalse(errStream.toString().contains("--truststore is required"));
+ assertFalse(errStream.toString().contains("--truststore-file is required"));
}
}
}
From 4bb3b8e91af76bf14d11532931850ad50cba43a4 Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Thu, 23 Apr 2026 20:02:51 +0530
Subject: [PATCH 09/28] safkeyring truststore bug fix
Signed-off-by: hrishikesh-nalawade
---
.../src/main/java/org/zowe/apiml/Stores.java | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
index ef7b527e67..9a93584375 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
@@ -93,8 +93,14 @@ private void initTruststore() throws IOException, CertificateException, NoSuchAl
}
return;
}
- try (InputStream trustStoreIStream = new FileInputStream(conf.getTrustStore())) {
- this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
+ if (isKeyring(conf.getTrustStore())) {
+ try (InputStream trustStoreIStream = keyRingUrl(conf.getTrustStore()).openStream()) {
+ this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
+ }
+ } else {
+ try (InputStream trustStoreIStream = new FileInputStream(conf.getTrustStore())) {
+ this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
+ }
}
}
From e2d0058a2890b35ff983176e1cbf528ab1e4de0e Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Sat, 25 Apr 2026 00:59:55 +0530
Subject: [PATCH 10/28] safkeyring truststore bug fix
Signed-off-by: hrishikesh-nalawade
---
.../main/java/org/zowe/apiml/ZosmfJwtCheck.java | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
index bb13cc57bc..e95093a748 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
@@ -29,6 +29,7 @@ public class ZosmfJwtCheck {
static final String VERIFY_DISABLED = "DISABLED";
public static int mainWithExitCode(String[] args) {
+ ensureSafkeyringHandler();
try {
ZosmfJwtCheckConf conf = new ZosmfJwtCheckConf();
CommandLine cmd = new CommandLine(conf);
@@ -98,6 +99,20 @@ static void validateConfig(ZosmfJwtCheckConf conf) {
}
}
+ /**
+ * Registers the IBM SAF keyring URL protocol handler so that
+ * {@code new URL("safkeyring://...")} works on z/OS without requiring the
+ * caller to pass {@code -Djava.protocol.handler.pkgs=com.ibm.crypto.provider}.
+ * On non-z/OS platforms the handler class is simply not found and is ignored.
+ */
+ static void ensureSafkeyringHandler() {
+ String existing = System.getProperty("java.protocol.handler.pkgs", "");
+ if (!existing.contains("com.ibm.crypto.provider")) {
+ System.setProperty("java.protocol.handler.pkgs",
+ existing.isEmpty() ? "com.ibm.crypto.provider" : existing + "|com.ibm.crypto.provider");
+ }
+ }
+
public static void main(String[] args) {
System.exit(mainWithExitCode(args));
}
From 2088e48998e9bd0d0f8aae917382dc7d05957c6b Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Wed, 13 May 2026 17:08:58 +0530
Subject: [PATCH 11/28] Debug Logs
Signed-off-by: hrishikesh-nalawade
---
.../org/zowe/apiml/HttpClientWrapper.java | 14 +++++
.../org/zowe/apiml/JwkEndpointChecker.java | 19 +++++++
.../org/zowe/apiml/SSLContextFactory.java | 6 +++
.../src/main/java/org/zowe/apiml/Stores.java | 52 +++++++++++++++++--
.../java/org/zowe/apiml/ZosmfJwtCheck.java | 30 +++++++++++
5 files changed, 118 insertions(+), 3 deletions(-)
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
index 8faca04157..8feecfa6e3 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
@@ -28,6 +28,7 @@
* Supports custom {@link SSLContext} and {@link HostnameVerifier} for
* STRICT, NONSTRICT, and DISABLED certificate verification modes.
*/
+// TODO: REMOVE all "DEBUG [HttpClientWrapper]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class HttpClientWrapper {
@@ -64,6 +65,11 @@ public HttpClientWrapper() {
}
public Response executeCall(URL url, Map headers) throws IOException {
+ System.out.println("DEBUG [HttpClientWrapper] executeCall() URL=" + url);
+ System.out.println("DEBUG [HttpClientWrapper] useHttps=" + useHttps);
+ System.out.println("DEBUG [HttpClientWrapper] sslContext=" + (sslContext != null ? sslContext.getProtocol() : ""));
+ System.out.println("DEBUG [HttpClientWrapper] hostnameVerifier=" + (hostnameVerifier != null ? hostnameVerifier.getClass().getName() : ""));
+
HttpURLConnection con;
if (useHttps) {
HttpsURLConnection httpsCon = (HttpsURLConnection) url.openConnection();
@@ -72,8 +78,10 @@ public Response executeCall(URL url, Map headers) throws IOExcep
httpsCon.setHostnameVerifier(hostnameVerifier);
}
con = httpsCon;
+ System.out.println("DEBUG [HttpClientWrapper] HTTPS connection opened");
} else {
con = (HttpURLConnection) url.openConnection();
+ System.out.println("DEBUG [HttpClientWrapper] HTTP connection opened");
}
con.setRequestMethod("GET");
@@ -86,10 +94,16 @@ public Response executeCall(URL url, Map headers) throws IOExcep
}
}
+ System.out.println("DEBUG [HttpClientWrapper] Sending GET request (connectTimeout=" + CONNECT_TIMEOUT + "ms, readTimeout=" + READ_TIMEOUT + "ms)...");
try {
int responseCode = con.getResponseCode();
+ System.out.println("DEBUG [HttpClientWrapper] Response code=" + responseCode);
String body = readBody(con);
+ System.out.println("DEBUG [HttpClientWrapper] Response body read, length=" + (body != null ? body.length() : ""));
return new Response(responseCode, body);
+ } catch (IOException e) {
+ System.err.println("DEBUG [HttpClientWrapper] IOException during request: " + e.getClass().getName() + ": " + e.getMessage());
+ throw e;
} finally {
con.disconnect();
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
index 06f5b0681a..1eedc7e8d3 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
@@ -24,6 +24,7 @@
* Checks z/OSMF JWK endpoint availability at {@code /jwt/ibm/api/zOSMFBuilder/jwk}.
* Interprets the HTTP response code to determine if the endpoint is functional
*/
+// TODO: REMOVE all "DEBUG [JwkEndpointChecker]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class JwkEndpointChecker {
@@ -45,11 +46,19 @@ public boolean check() {
Map headers = new HashMap<>();
headers.put(ZOSMF_CSRF_HEADER, "");
+ System.out.println("DEBUG [JwkEndpointChecker] check() called");
+ System.out.println("DEBUG [JwkEndpointChecker] target URL=" + urlString);
+ System.out.println("DEBUG [JwkEndpointChecker] headers=" + headers);
+
try {
URL url = new URL(urlString);
System.out.println("Checking z/OSMF JWK endpoint: " + urlString);
+ System.out.println("DEBUG [JwkEndpointChecker] Calling httpClient.executeCall()...");
HttpClientWrapper.Response response = httpClient.executeCall(url, headers);
+
+ System.out.println("DEBUG [JwkEndpointChecker] Response received: HTTP " + response.getStatusCode());
+ System.out.println("DEBUG [JwkEndpointChecker] Response body length=" + (response.getBody() != null ? response.getBody().length() : ""));
if (conf.isVerbose() && response.getBody() != null) {
System.out.println("Response body:\n" + response.getBody());
}
@@ -58,24 +67,34 @@ public boolean check() {
System.err.println("FAILURE: SSL handshake failed when connecting to " + urlString + ".");
System.err.println("Verify that the truststore contains the z/OSMF server certificate.");
System.err.println("Details: " + e.getMessage());
+ System.err.println("DEBUG [JwkEndpointChecker] SSLHandshakeException stack trace:");
+ e.printStackTrace(System.err);
return false;
} catch (ConnectException e) {
System.err.println("FAILURE: Cannot connect to " + conf.getZosmfHost() + ":" + conf.getZosmfPort() + ".");
System.err.println("Verify the host and port are correct and z/OSMF is running.");
System.err.println("Details: " + e.getMessage());
+ System.err.println("DEBUG [JwkEndpointChecker] ConnectException stack trace:");
+ e.printStackTrace(System.err);
return false;
} catch (SocketTimeoutException e) {
System.err.println("FAILURE: Connection timed out to " + conf.getZosmfHost() + ":" + conf.getZosmfPort() + ".");
System.err.println("This is commonly caused by an incorrect host/port or a firewall blocking the connection.");
System.err.println("Verify the z/OSMF host and port are correct and that no firewall is blocking access.");
+ System.err.println("DEBUG [JwkEndpointChecker] SocketTimeoutException stack trace:");
+ e.printStackTrace(System.err);
return false;
} catch (UnknownHostException e) {
System.err.println("FAILURE: Error when calling " + urlString + " verify hostname and port.");
System.err.println("The host '" + conf.getZosmfHost() + "' could not be resolved.");
+ System.err.println("DEBUG [JwkEndpointChecker] UnknownHostException stack trace:");
+ e.printStackTrace(System.err);
return false;
} catch (Exception e) {
System.err.println("FAILURE: Error when calling " + urlString + " verify hostname and port.");
System.err.println("Details: " + e.getMessage());
+ System.err.println("DEBUG [JwkEndpointChecker] Exception class=" + e.getClass().getName());
+ e.printStackTrace(System.err);
return false;
}
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
index 8e4f8cedaf..c3e1b034a0 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
@@ -27,6 +27,7 @@
* {@link #initTrustAllSSLContext()} — trust-all mode for DISABLED verification
*
*/
+// TODO: REMOVE all "DEBUG [SSLContextFactory]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class SSLContextFactory {
@@ -48,15 +49,19 @@ public SSLContext getSslContext() {
* @return factory holding the initialized SSLContext
*/
public static SSLContextFactory initSSLContext(Stores stores) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException, CertificateException, IOException {
+ System.out.println("DEBUG [SSLContextFactory] initSSLContext() called");
SSLContextFactory factory = new SSLContextFactory(stores);
+ System.out.println("DEBUG [SSLContextFactory] Initializing TrustManagerFactory with trustStore (type=" + stores.getTrustStore().getType() + ", size=" + stores.getTrustStore().size() + ")");
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(stores.getTrustStore());
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
if (stores.getKeyStore() != null) {
+ System.out.println("DEBUG [SSLContextFactory] Initializing KeyManagerFactory with keyStore (type=" + stores.getKeyStore().getType() + ", size=" + stores.getKeyStore().size() + ")");
keyFactory.init(stores.getKeyStore(), stores.getConf().getKeyStorePassword().toCharArray());
} else {
+ System.out.println("DEBUG [SSLContextFactory] No keyStore, using empty keystore for KeyManagerFactory");
KeyStore emptyKeystore = KeyStore.getInstance(KeyStore.getDefaultType());
emptyKeystore.load(null, null);
keyFactory.init(emptyKeystore, null);
@@ -64,6 +69,7 @@ public static SSLContextFactory initSSLContext(Stores stores) throws NoSuchAlgor
factory.sslContext = SSLContext.getInstance("TLSv1.2");
factory.sslContext.init(keyFactory.getKeyManagers(), trustFactory.getTrustManagers(), new SecureRandom());
+ System.out.println("DEBUG [SSLContextFactory] SSLContext created successfully (protocol=" + factory.sslContext.getProtocol() + ")");
return factory;
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
index 9a93584375..4ff78fefcc 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
@@ -27,6 +27,7 @@
* Loads Java {@link java.security.KeyStore} instances from the filesystem
* or z/OS SAF keyrings. Supports PKCS12, JKS, and {@code safkeyring://} URIs.
*/
+// TODO: REMOVE all "DEBUG [Stores]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class Stores {
@@ -69,22 +70,38 @@ public static String formatKeyringUrl(String input) {
}
void init() {
+ System.out.println("DEBUG [Stores] init() called");
+ System.out.println("DEBUG [Stores] trustStore path=" + conf.getTrustStore());
+ System.out.println("DEBUG [Stores] trustStore type=" + conf.getTrustStoreType());
+ System.out.println("DEBUG [Stores] trustStore isKeyring=" + isKeyring(conf.getTrustStore()));
+ System.out.println("DEBUG [Stores] keyStore path=" + conf.getKeyStore());
+ System.out.println("DEBUG [Stores] keyStore type=" + (conf.getKeyStore() != null ? conf.getKeyStoreType() : ""));
+ System.out.println("DEBUG [Stores] keyStore isKeyring=" + isKeyring(conf.getKeyStore()));
+ System.out.println("DEBUG [Stores] java.protocol.handler.pkgs=" + System.getProperty("java.protocol.handler.pkgs", ""));
try {
+ System.out.println("DEBUG [Stores] Calling initKeystore()...");
initKeystore();
+ System.out.println("DEBUG [Stores] initKeystore() completed. trustStore set by keystore=" + (trustStore != null));
if (trustStore == null) {
+ System.out.println("DEBUG [Stores] Calling initTruststore()...");
initTruststore();
+ System.out.println("DEBUG [Stores] initTruststore() completed successfully.");
}
} catch (FileNotFoundException e) {
+ System.err.println("DEBUG [Stores] FileNotFoundException in init(): " + e.getMessage());
+ e.printStackTrace(System.err);
throw new StoresNotInitializeException("Error while loading keystore file. Error message: " + e.getMessage() + "\n" +
"Possible solution: Verify correct path to the keystore. Change owner or permission to the keystore file.");
} catch (Exception e) {
+ System.err.println("DEBUG [Stores] Exception in init(): " + e.getClass().getName() + ": " + e.getMessage());
+ e.printStackTrace(System.err);
throw new StoresNotInitializeException(e.getMessage());
}
}
private void initTruststore() throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
if (conf.getTrustStore() == null) {
- System.out.println("No truststore specified, will use empty.");
+ System.out.println("DEBUG [Stores] initTruststore: No truststore specified, will use empty.");
try {
this.trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
this.trustStore.load(null, null);
@@ -94,30 +111,48 @@ private void initTruststore() throws IOException, CertificateException, NoSuchAl
return;
}
if (isKeyring(conf.getTrustStore())) {
- try (InputStream trustStoreIStream = keyRingUrl(conf.getTrustStore()).openStream()) {
+ System.out.println("DEBUG [Stores] initTruststore: Detected SAF keyring URI: " + conf.getTrustStore());
+ String formatted = formatKeyringUrl(conf.getTrustStore());
+ System.out.println("DEBUG [Stores] initTruststore: Formatted keyring URL: " + formatted);
+ System.out.println("DEBUG [Stores] initTruststore: Calling keyRingUrl()...");
+ URL url = keyRingUrl(conf.getTrustStore());
+ System.out.println("DEBUG [Stores] initTruststore: URL object created: " + url + " (protocol=" + url.getProtocol() + ")");
+ System.out.println("DEBUG [Stores] initTruststore: Calling url.openStream()...");
+ try (InputStream trustStoreIStream = url.openStream()) {
+ System.out.println("DEBUG [Stores] initTruststore: openStream() succeeded. Loading keystore type=" + conf.getTrustStoreType());
this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
+ System.out.println("DEBUG [Stores] initTruststore: Truststore loaded from keyring. Aliases count=" + trustStore.size());
}
} else {
+ System.out.println("DEBUG [Stores] initTruststore: Loading from file: " + conf.getTrustStore());
try (InputStream trustStoreIStream = new FileInputStream(conf.getTrustStore())) {
this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
+ System.out.println("DEBUG [Stores] initTruststore: Truststore loaded from file. Aliases count=" + trustStore.size());
}
}
}
private void initKeystore() throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
if (conf.getKeyStore() == null) {
+ System.out.println("DEBUG [Stores] initKeystore: No keystore specified, skipping.");
return;
}
if (isKeyring(conf.getKeyStore())) {
+ System.out.println("DEBUG [Stores] initKeystore: Detected SAF keyring URI: " + conf.getKeyStore());
try (InputStream keyringIStream = keyRingUrl(conf.getKeyStore()).openStream()) {
this.keyStore = readKeyStore(keyringIStream, conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
this.trustStore = this.keyStore;
+ System.out.println("DEBUG [Stores] initKeystore: Keystore loaded from keyring. Aliases count=" + keyStore.size());
} catch (Exception e) {
+ System.err.println("DEBUG [Stores] initKeystore: Exception loading keyring: " + e.getClass().getName() + ": " + e.getMessage());
+ e.printStackTrace(System.err);
throw new StoresNotInitializeException(e.getMessage());
}
} else {
+ System.out.println("DEBUG [Stores] initKeystore: Loading from file: " + conf.getKeyStore());
try (InputStream keyStoreIStream = new FileInputStream(conf.getKeyStore())) {
this.keyStore = readKeyStore(keyStoreIStream, conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
+ System.out.println("DEBUG [Stores] initKeystore: Keystore loaded from file. Aliases count=" + keyStore.size());
}
}
}
@@ -145,6 +180,17 @@ public static URL keyRingUrl(String uri) throws MalformedURLException {
throw new StoresNotInitializeException("Incorrect key ring format: " + uri
+ ". Make sure you use format safkeyring://userId/keyRing");
}
- return new URL(formatKeyringUrl(uri));
+ String formatted = formatKeyringUrl(uri);
+ System.out.println("DEBUG [Stores] keyRingUrl: Creating URL from: " + formatted);
+ try {
+ URL url = new URL(formatted);
+ System.out.println("DEBUG [Stores] keyRingUrl: URL created successfully. protocol=" + url.getProtocol()
+ + " host=" + url.getHost() + " path=" + url.getPath());
+ return url;
+ } catch (MalformedURLException e) {
+ System.err.println("DEBUG [Stores] keyRingUrl: MalformedURLException for '" + formatted + "': " + e.getMessage());
+ System.err.println("DEBUG [Stores] keyRingUrl: java.protocol.handler.pkgs=" + System.getProperty("java.protocol.handler.pkgs", ""));
+ throw e;
+ }
}
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
index e95093a748..1144755085 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
@@ -21,6 +21,7 @@
*
* Exit codes: 0 = success, 4 = failure/error, 8 = help displayed.
*/
+// TODO: REMOVE all "DEBUG [ZosmfJwtCheck]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class ZosmfJwtCheck {
@@ -29,7 +30,18 @@ public class ZosmfJwtCheck {
static final String VERIFY_DISABLED = "DISABLED";
public static int mainWithExitCode(String[] args) {
+ System.out.println("DEBUG [ZosmfJwtCheck] ===== zosmf-jwt-check starting =====");
+ System.out.println("DEBUG [ZosmfJwtCheck] Java version: " + System.getProperty("java.version"));
+ System.out.println("DEBUG [ZosmfJwtCheck] Java vendor: " + System.getProperty("java.vendor"));
+ System.out.println("DEBUG [ZosmfJwtCheck] OS name: " + System.getProperty("os.name"));
+ System.out.println("DEBUG [ZosmfJwtCheck] java.protocol.handler.pkgs (BEFORE ensureSafkeyringHandler): "
+ + System.getProperty("java.protocol.handler.pkgs", ""));
+
ensureSafkeyringHandler();
+
+ System.out.println("DEBUG [ZosmfJwtCheck] java.protocol.handler.pkgs (AFTER ensureSafkeyringHandler): "
+ + System.getProperty("java.protocol.handler.pkgs", ""));
+
try {
ZosmfJwtCheckConf conf = new ZosmfJwtCheckConf();
CommandLine cmd = new CommandLine(conf);
@@ -41,6 +53,17 @@ public static int mainWithExitCode(String[] args) {
return 8;
}
+ System.out.println("DEBUG [ZosmfJwtCheck] Parsed config:");
+ System.out.println("DEBUG [ZosmfJwtCheck] zosmf-host=" + conf.getZosmfHost());
+ System.out.println("DEBUG [ZosmfJwtCheck] zosmf-port=" + conf.getZosmfPort());
+ System.out.println("DEBUG [ZosmfJwtCheck] scheme=" + conf.getScheme());
+ System.out.println("DEBUG [ZosmfJwtCheck] verify-certificates=" + conf.getVerifyCertificates());
+ System.out.println("DEBUG [ZosmfJwtCheck] truststore-file=" + conf.getTrustStore());
+ System.out.println("DEBUG [ZosmfJwtCheck] truststore-type=" + conf.getTrustStoreType());
+ System.out.println("DEBUG [ZosmfJwtCheck] truststore-isKeyring=" + Stores.isKeyring(conf.getTrustStore()));
+ System.out.println("DEBUG [ZosmfJwtCheck] keystore-file=" + conf.getKeyStore());
+ System.out.println("DEBUG [ZosmfJwtCheck] keystore-type=" + conf.getKeyStoreType());
+
validateConfig(conf);
HttpClientWrapper httpClient;
@@ -52,8 +75,11 @@ public static int mainWithExitCode(String[] args) {
HostnameVerifier noopVerifier = (hostname, session) -> true;
httpClient = new HttpClientWrapper(sslContextFactory.getSslContext(), noopVerifier);
} else {
+ System.out.println("DEBUG [ZosmfJwtCheck] Creating Stores (truststore/keystore)...");
Stores stores = new Stores(conf);
+ System.out.println("DEBUG [ZosmfJwtCheck] Stores created successfully. Building SSLContext...");
SSLContextFactory sslContextFactory = SSLContextFactory.initSSLContext(stores);
+ System.out.println("DEBUG [ZosmfJwtCheck] SSLContext created successfully.");
HostnameVerifier hostnameVerifier;
if (VERIFY_NONSTRICT.equals(verifyMode)) {
@@ -69,10 +95,14 @@ public static int mainWithExitCode(String[] args) {
}
JwkEndpointChecker checker = new JwkEndpointChecker(httpClient, conf);
+ System.out.println("DEBUG [ZosmfJwtCheck] Calling JwkEndpointChecker.check()...");
boolean success = checker.check();
+ System.out.println("DEBUG [ZosmfJwtCheck] JwkEndpointChecker.check() returned: " + success);
return success ? 0 : 4;
} catch (Exception e) {
System.err.println("ERROR: " + e.getMessage());
+ System.err.println("DEBUG [ZosmfJwtCheck] Exception class: " + e.getClass().getName());
+ e.printStackTrace(System.err);
return 4;
}
}
From fc8c14f65b704344d700b7ca5991b0f3a0389ca4 Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Thu, 14 May 2026 00:41:11 +0530
Subject: [PATCH 12/28] further fix
Signed-off-by: hrishikesh-nalawade
---
.../src/main/java/org/zowe/apiml/Stores.java | 85 +++++++++++++++++--
.../java/org/zowe/apiml/ZosmfJwtCheck.java | 59 ++++++++++++-
2 files changed, 133 insertions(+), 11 deletions(-)
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
index 4ff78fefcc..8dad9b614d 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
@@ -14,6 +14,7 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyStore;
@@ -112,14 +113,9 @@ private void initTruststore() throws IOException, CertificateException, NoSuchAl
}
if (isKeyring(conf.getTrustStore())) {
System.out.println("DEBUG [Stores] initTruststore: Detected SAF keyring URI: " + conf.getTrustStore());
- String formatted = formatKeyringUrl(conf.getTrustStore());
- System.out.println("DEBUG [Stores] initTruststore: Formatted keyring URL: " + formatted);
- System.out.println("DEBUG [Stores] initTruststore: Calling keyRingUrl()...");
- URL url = keyRingUrl(conf.getTrustStore());
- System.out.println("DEBUG [Stores] initTruststore: URL object created: " + url + " (protocol=" + url.getProtocol() + ")");
- System.out.println("DEBUG [Stores] initTruststore: Calling url.openStream()...");
- try (InputStream trustStoreIStream = url.openStream()) {
- System.out.println("DEBUG [Stores] initTruststore: openStream() succeeded. Loading keystore type=" + conf.getTrustStoreType());
+ System.out.println("DEBUG [Stores] initTruststore: Opening keyring stream (URL with RACFInputStream fallback)...");
+ try (InputStream trustStoreIStream = openKeyringStream(conf.getTrustStore(), conf.getTrustStorePassword().toCharArray())) {
+ System.out.println("DEBUG [Stores] initTruststore: Stream opened. Loading keystore type=" + conf.getTrustStoreType());
this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
System.out.println("DEBUG [Stores] initTruststore: Truststore loaded from keyring. Aliases count=" + trustStore.size());
}
@@ -139,7 +135,7 @@ private void initKeystore() throws IOException, CertificateException, NoSuchAlgo
}
if (isKeyring(conf.getKeyStore())) {
System.out.println("DEBUG [Stores] initKeystore: Detected SAF keyring URI: " + conf.getKeyStore());
- try (InputStream keyringIStream = keyRingUrl(conf.getKeyStore()).openStream()) {
+ try (InputStream keyringIStream = openKeyringStream(conf.getKeyStore(), conf.getKeyStorePassword().toCharArray())) {
this.keyStore = readKeyStore(keyringIStream, conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
this.trustStore = this.keyStore;
System.out.println("DEBUG [Stores] initKeystore: Keystore loaded from keyring. Aliases count=" + keyStore.size());
@@ -193,4 +189,75 @@ public static URL keyRingUrl(String uri) throws MalformedURLException {
throw e;
}
}
+
+ /**
+ * Opens an InputStream to a SAF keyring. Tries the standard URL-based approach first,
+ * then falls back to IBM's RACFInputStream (loaded via reflection) if the URL protocol
+ * handler is not available.
+ */
+ // TODO: REMOVE debug logging after SAF keyring issue is resolved
+ @SuppressWarnings("squid:S3011")
+ static InputStream openKeyringStream(String uri, char[] password) throws IOException {
+ String formatted = formatKeyringUrl(uri);
+ System.out.println("DEBUG [Stores] openKeyringStream: uri='" + uri + "' formatted='" + formatted + "'");
+ System.out.println("DEBUG [Stores] openKeyringStream: java.protocol.handler.pkgs="
+ + System.getProperty("java.protocol.handler.pkgs", ""));
+
+ // Try standard URL-based approach first (same as main API ML services)
+ try {
+ System.out.println("DEBUG [Stores] openKeyringStream: Attempting new URL('" + formatted + "')...");
+ URL url = new URL(formatted);
+ System.out.println("DEBUG [Stores] openKeyringStream: URL created. protocol=" + url.getProtocol()
+ + " host=" + url.getHost() + " path=" + url.getPath()
+ + " class=" + url.getClass().getName());
+ System.out.println("DEBUG [Stores] openKeyringStream: Calling url.openStream()...");
+ InputStream is = url.openStream();
+ System.out.println("DEBUG [Stores] openKeyringStream: URL.openStream() SUCCEEDED (stream class=" + is.getClass().getName() + ")");
+ return is;
+ } catch (MalformedURLException urlEx) {
+ System.err.println("DEBUG [Stores] openKeyringStream: URL approach FAILED with MalformedURLException: " + urlEx.getMessage());
+ } catch (IOException ioEx) {
+ System.err.println("DEBUG [Stores] openKeyringStream: URL.openStream() FAILED with IOException: "
+ + ioEx.getClass().getName() + ": " + ioEx.getMessage());
+ ioEx.printStackTrace(System.err);
+ // Re-throw IO errors from openStream — the URL was valid but the stream failed
+ throw ioEx;
+ }
+
+ // Fallback: use com.ibm.crypto.zsecurity.provider.RACFInputStream via reflection
+ // This class is available on z/OS IBM JDKs and bypasses the URL protocol handler
+ System.out.println("DEBUG [Stores] openKeyringStream: URL handler not available, attempting RACFInputStream fallback...");
+
+ Matcher matcher = KEYRING_PATTERN.matcher(uri);
+ if (!matcher.matches()) {
+ matcher = KEYRING_PATTERN.matcher(formatted);
+ if (!matcher.matches()) {
+ throw new IOException("Cannot open keyring: invalid URI format: " + uri);
+ }
+ }
+
+ String userId = matcher.group(2);
+ String ringName = matcher.group(3);
+ System.out.println("DEBUG [Stores] openKeyringStream: Parsed userId='" + userId + "' ringName='" + ringName + "'");
+
+ try {
+ System.out.println("DEBUG [Stores] openKeyringStream: Loading class com.ibm.crypto.zsecurity.provider.RACFInputStream...");
+ Class> racfClass = Class.forName("com.ibm.crypto.zsecurity.provider.RACFInputStream");
+ System.out.println("DEBUG [Stores] openKeyringStream: RACFInputStream class loaded (classLoader=" + racfClass.getClassLoader() + ")");
+ Constructor> ctor = racfClass.getConstructor(String.class, String.class, char[].class);
+ System.out.println("DEBUG [Stores] openKeyringStream: Invoking RACFInputStream('" + userId + "', '" + ringName + "', )...");
+ InputStream is = (InputStream) ctor.newInstance(userId, ringName, password);
+ System.out.println("DEBUG [Stores] openKeyringStream: RACFInputStream SUCCEEDED (stream class=" + is.getClass().getName() + ")");
+ return is;
+ } catch (ClassNotFoundException cnfe) {
+ System.err.println("DEBUG [Stores] openKeyringStream: RACFInputStream class NOT FOUND: " + cnfe.getMessage());
+ throw new IOException("Cannot open keyring '" + uri + "': URL protocol handler not available " +
+ "and RACFInputStream class not found. Ensure running on z/OS with IBM JDK.", cnfe);
+ } catch (Exception e) {
+ System.err.println("DEBUG [Stores] openKeyringStream: RACFInputStream FAILED: " + e.getClass().getName() + ": " + e.getMessage());
+ e.printStackTrace(System.err);
+ throw new IOException("Cannot open keyring '" + uri + "': URL protocol handler not available " +
+ "and RACFInputStream fallback failed: " + e.getMessage(), e);
+ }
+ }
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
index 1144755085..45a92ed472 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
@@ -33,7 +33,13 @@ public static int mainWithExitCode(String[] args) {
System.out.println("DEBUG [ZosmfJwtCheck] ===== zosmf-jwt-check starting =====");
System.out.println("DEBUG [ZosmfJwtCheck] Java version: " + System.getProperty("java.version"));
System.out.println("DEBUG [ZosmfJwtCheck] Java vendor: " + System.getProperty("java.vendor"));
+ System.out.println("DEBUG [ZosmfJwtCheck] Java home: " + System.getProperty("java.home"));
System.out.println("DEBUG [ZosmfJwtCheck] OS name: " + System.getProperty("os.name"));
+ System.out.println("DEBUG [ZosmfJwtCheck] java.class.path: " + System.getProperty("java.class.path", ""));
+ System.out.println("DEBUG [ZosmfJwtCheck] java.ext.dirs: " + System.getProperty("java.ext.dirs", ""));
+ System.out.println("DEBUG [ZosmfJwtCheck] Classloader chain: app=" + ZosmfJwtCheck.class.getClassLoader()
+ + " parent=" + (ZosmfJwtCheck.class.getClassLoader() != null ? ZosmfJwtCheck.class.getClassLoader().getParent() : "null")
+ + " threadCtx=" + Thread.currentThread().getContextClassLoader());
System.out.println("DEBUG [ZosmfJwtCheck] java.protocol.handler.pkgs (BEFORE ensureSafkeyringHandler): "
+ System.getProperty("java.protocol.handler.pkgs", ""));
@@ -137,9 +143,58 @@ static void validateConfig(ZosmfJwtCheckConf conf) {
*/
static void ensureSafkeyringHandler() {
String existing = System.getProperty("java.protocol.handler.pkgs", "");
+ System.out.println("DEBUG [ZosmfJwtCheck] ensureSafkeyringHandler: current value='" + existing + "'");
if (!existing.contains("com.ibm.crypto.provider")) {
- System.setProperty("java.protocol.handler.pkgs",
- existing.isEmpty() ? "com.ibm.crypto.provider" : existing + "|com.ibm.crypto.provider");
+ String newValue = existing.isEmpty() ? "com.ibm.crypto.provider" : existing + "|com.ibm.crypto.provider";
+ System.setProperty("java.protocol.handler.pkgs", newValue);
+ System.out.println("DEBUG [ZosmfJwtCheck] ensureSafkeyringHandler: SET to '" + newValue + "'");
+ } else {
+ System.out.println("DEBUG [ZosmfJwtCheck] ensureSafkeyringHandler: already contains com.ibm.crypto.provider, no change");
+ }
+
+ // TODO: REMOVE diagnostic checks after SAF keyring issue is resolved
+ // Check which IBM z/OS keyring-related classes are available on the classpath
+ String[] classesToCheck = {
+ "com.ibm.crypto.provider.safkeyring.Handler",
+ "com.ibm.crypto.provider.safkeyringjce.Handler",
+ "com.ibm.crypto.provider.safkeyringjcecca.Handler",
+ "com.ibm.crypto.provider.safkeyringjceccaracfks.Handler",
+ "com.ibm.crypto.provider.safkeyringjcehybrid.Handler",
+ "com.ibm.crypto.zsecurity.provider.RACFInputStream",
+ "com.ibm.jsse2.IBMJSSEProvider2",
+ };
+ for (String className : classesToCheck) {
+ try {
+ Class> cls = Class.forName(className);
+ System.out.println("DEBUG [ZosmfJwtCheck] Class FOUND: " + className
+ + " (classLoader=" + cls.getClassLoader() + ")");
+ } catch (ClassNotFoundException e) {
+ System.out.println("DEBUG [ZosmfJwtCheck] Class NOT FOUND: " + className);
+ }
+ }
+
+ // List installed security providers
+ System.out.println("DEBUG [ZosmfJwtCheck] Installed security providers:");
+ for (java.security.Provider p : java.security.Security.getProviders()) {
+ System.out.println("DEBUG [ZosmfJwtCheck] " + p.getName() + " v" + p.getVersionStr()
+ + " (" + p.getClass().getName() + ")");
+ }
+
+ // Test URL creation inline to capture exact failure point
+ System.out.println("DEBUG [ZosmfJwtCheck] Testing URL creation for 'safkeyring://test/test'...");
+ try {
+ java.net.URL testUrl = new java.net.URL("safkeyring://test/test");
+ System.out.println("DEBUG [ZosmfJwtCheck] URL creation SUCCEEDED: " + testUrl);
+ } catch (java.net.MalformedURLException e) {
+ System.out.println("DEBUG [ZosmfJwtCheck] URL creation FAILED: " + e.getMessage());
+ // If the handler class was FOUND above but URL creation FAILED, that confirms
+ // the bootstrap classloader theory: Class.forName from our code can find it,
+ // but java.net.URL (loaded by bootstrap classloader) cannot.
+ System.out.println("DEBUG [ZosmfJwtCheck] URL class classLoader: " + java.net.URL.class.getClassLoader());
+ System.out.println("DEBUG [ZosmfJwtCheck] This class classLoader: " + ZosmfJwtCheck.class.getClassLoader());
+ System.out.println("DEBUG [ZosmfJwtCheck] If handler class was FOUND above but URL creation FAILED,");
+ System.out.println("DEBUG [ZosmfJwtCheck] this indicates a classloader visibility issue.");
+ System.out.println("DEBUG [ZosmfJwtCheck] The RACFInputStream fallback in openKeyringStream() will be used instead.");
}
}
From 2b566bd4b4a44daee58998cd7b970606b353bb1e Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Thu, 14 May 2026 02:23:09 +0530
Subject: [PATCH 13/28] further fix
Signed-off-by: hrishikesh-nalawade
---
.../src/main/java/org/zowe/apiml/Stores.java | 195 ++++++++++++------
1 file changed, 137 insertions(+), 58 deletions(-)
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
index 8dad9b614d..5dcc868a41 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
@@ -15,11 +15,14 @@
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.Security;
import java.security.cert.CertificateException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -113,12 +116,9 @@ private void initTruststore() throws IOException, CertificateException, NoSuchAl
}
if (isKeyring(conf.getTrustStore())) {
System.out.println("DEBUG [Stores] initTruststore: Detected SAF keyring URI: " + conf.getTrustStore());
- System.out.println("DEBUG [Stores] initTruststore: Opening keyring stream (URL with RACFInputStream fallback)...");
- try (InputStream trustStoreIStream = openKeyringStream(conf.getTrustStore(), conf.getTrustStorePassword().toCharArray())) {
- System.out.println("DEBUG [Stores] initTruststore: Stream opened. Loading keystore type=" + conf.getTrustStoreType());
- this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
- System.out.println("DEBUG [Stores] initTruststore: Truststore loaded from keyring. Aliases count=" + trustStore.size());
- }
+ System.out.println("DEBUG [Stores] initTruststore: Loading keyring truststore (multi-strategy)...");
+ this.trustStore = loadKeyringKeyStore(conf.getTrustStore(), conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
+ System.out.println("DEBUG [Stores] initTruststore: Truststore loaded from keyring. Aliases count=" + trustStore.size());
} else {
System.out.println("DEBUG [Stores] initTruststore: Loading from file: " + conf.getTrustStore());
try (InputStream trustStoreIStream = new FileInputStream(conf.getTrustStore())) {
@@ -135,8 +135,8 @@ private void initKeystore() throws IOException, CertificateException, NoSuchAlgo
}
if (isKeyring(conf.getKeyStore())) {
System.out.println("DEBUG [Stores] initKeystore: Detected SAF keyring URI: " + conf.getKeyStore());
- try (InputStream keyringIStream = openKeyringStream(conf.getKeyStore(), conf.getKeyStorePassword().toCharArray())) {
- this.keyStore = readKeyStore(keyringIStream, conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
+ try {
+ this.keyStore = loadKeyringKeyStore(conf.getKeyStore(), conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
this.trustStore = this.keyStore;
System.out.println("DEBUG [Stores] initKeystore: Keystore loaded from keyring. Aliases count=" + keyStore.size());
} catch (Exception e) {
@@ -191,73 +191,152 @@ public static URL keyRingUrl(String uri) throws MalformedURLException {
}
/**
- * Opens an InputStream to a SAF keyring. Tries the standard URL-based approach first,
- * then falls back to IBM's RACFInputStream (loaded via reflection) if the URL protocol
- * handler is not available.
+ * Loads a KeyStore from a SAF keyring using multiple strategies in order:
+ *
+ * - Standard URL approach: {@code new URL("safkeyring://...").openStream()}
+ * - RACFInputStream via reflection (bypasses URL handler)
+ * - Direct JCERACFKS load via IBMZSecurity provider (no InputStream needed)
+ *
*/
// TODO: REMOVE debug logging after SAF keyring issue is resolved
@SuppressWarnings("squid:S3011")
- static InputStream openKeyringStream(String uri, char[] password) throws IOException {
+ static KeyStore loadKeyringKeyStore(String uri, char[] password, String type) throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
String formatted = formatKeyringUrl(uri);
- System.out.println("DEBUG [Stores] openKeyringStream: uri='" + uri + "' formatted='" + formatted + "'");
- System.out.println("DEBUG [Stores] openKeyringStream: java.protocol.handler.pkgs="
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: uri='" + uri + "' formatted='" + formatted + "' type=" + type);
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: java.protocol.handler.pkgs="
+ System.getProperty("java.protocol.handler.pkgs", ""));
- // Try standard URL-based approach first (same as main API ML services)
+ Matcher matcher = KEYRING_PATTERN.matcher(uri);
+ if (!matcher.matches()) {
+ matcher = KEYRING_PATTERN.matcher(formatted);
+ }
+ String userId = matcher.matches() ? matcher.group(2) : null;
+ String ringName = matcher.matches() ? matcher.group(3) : null;
+
+ // Strategy 1: Standard URL-based approach (same as main API ML services)
try {
- System.out.println("DEBUG [Stores] openKeyringStream: Attempting new URL('" + formatted + "')...");
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 1 - URL approach...");
URL url = new URL(formatted);
- System.out.println("DEBUG [Stores] openKeyringStream: URL created. protocol=" + url.getProtocol()
- + " host=" + url.getHost() + " path=" + url.getPath()
- + " class=" + url.getClass().getName());
- System.out.println("DEBUG [Stores] openKeyringStream: Calling url.openStream()...");
- InputStream is = url.openStream();
- System.out.println("DEBUG [Stores] openKeyringStream: URL.openStream() SUCCEEDED (stream class=" + is.getClass().getName() + ")");
- return is;
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: URL created. protocol=" + url.getProtocol());
+ try (InputStream is = url.openStream()) {
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: URL.openStream() SUCCEEDED (stream class=" + is.getClass().getName() + ")");
+ KeyStore ks = KeyStore.getInstance(type);
+ ks.load(is, password);
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 1 SUCCEEDED. Aliases count=" + ks.size());
+ return ks;
+ }
} catch (MalformedURLException urlEx) {
- System.err.println("DEBUG [Stores] openKeyringStream: URL approach FAILED with MalformedURLException: " + urlEx.getMessage());
+ System.err.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 1 FAILED (MalformedURLException): " + urlEx.getMessage());
} catch (IOException ioEx) {
- System.err.println("DEBUG [Stores] openKeyringStream: URL.openStream() FAILED with IOException: "
+ System.err.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 1 FAILED (IOException on openStream): "
+ ioEx.getClass().getName() + ": " + ioEx.getMessage());
- ioEx.printStackTrace(System.err);
- // Re-throw IO errors from openStream — the URL was valid but the stream failed
- throw ioEx;
}
- // Fallback: use com.ibm.crypto.zsecurity.provider.RACFInputStream via reflection
- // This class is available on z/OS IBM JDKs and bypasses the URL protocol handler
- System.out.println("DEBUG [Stores] openKeyringStream: URL handler not available, attempting RACFInputStream fallback...");
-
- Matcher matcher = KEYRING_PATTERN.matcher(uri);
- if (!matcher.matches()) {
- matcher = KEYRING_PATTERN.matcher(formatted);
- if (!matcher.matches()) {
- throw new IOException("Cannot open keyring: invalid URI format: " + uri);
+ // Strategy 2: RACFInputStream via reflection
+ if (userId != null && ringName != null) {
+ try {
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 2 - RACFInputStream('" + userId + "', '" + ringName + "')...");
+ Class> racfClass = Class.forName("com.ibm.crypto.zsecurity.provider.RACFInputStream");
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: RACFInputStream class loaded (classLoader=" + racfClass.getClassLoader() + ")");
+ Constructor> ctor = racfClass.getConstructor(String.class, String.class, char[].class);
+ InputStream is = (InputStream) ctor.newInstance(userId, ringName, password);
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: RACFInputStream created. Loading KeyStore...");
+ KeyStore ks = KeyStore.getInstance(type);
+ ks.load(is, password);
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 2 SUCCEEDED. Aliases count=" + ks.size());
+ return ks;
+ } catch (InvocationTargetException ite) {
+ Throwable cause = ite.getCause();
+ String causeMsg = cause != null ? cause.getMessage() : ite.getMessage();
+ System.err.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 2 FAILED (RACFInputStream constructor threw): " + causeMsg);
+ if (cause != null) {
+ cause.printStackTrace(System.err);
+ }
+ // If the error is about private key access, try Strategy 3
+ if (cause instanceof IOException && causeMsg != null && causeMsg.contains("private key")) {
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: Private key permission error detected."
+ + " This likely means the running user does not have RACF authority to access"
+ + " private keys in keyring " + userId + "/" + ringName + "."
+ + " Trying Strategy 3 (direct JCERACFKS load)...");
+ } else {
+ // Non-private-key error — still try Strategy 3 but log the original error
+ System.err.println("DEBUG [Stores] loadKeyringKeyStore: RACFInputStream failed with non-private-key error. Trying Strategy 3...");
+ }
+ } catch (ClassNotFoundException cnfe) {
+ System.err.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 2 FAILED (class not found): " + cnfe.getMessage());
+ } catch (Exception e) {
+ System.err.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 2 FAILED: " + e.getClass().getName() + ": " + e.getMessage());
+ e.printStackTrace(System.err);
}
}
- String userId = matcher.group(2);
- String ringName = matcher.group(3);
- System.out.println("DEBUG [Stores] openKeyringStream: Parsed userId='" + userId + "' ringName='" + ringName + "'");
+ // Strategy 3: Direct JCERACFKS KeyStore load using IBMZSecurity provider
+ // On IBM z/OS JDK, the JCERACFKS KeyStore SPI can load keyrings directly
+ // when the keyring URL is provided as the password/loadStoreParameter.
+ // This approach does NOT use RACFInputStream and may handle inaccessible
+ // private keys gracefully (loading only certificates the user can access).
+ Provider ibmZSecurityProvider = Security.getProvider("IBMZSecurity");
+ if (ibmZSecurityProvider != null) {
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3 - Direct JCERACFKS via IBMZSecurity provider...");
+ try {
+ KeyStore ks = KeyStore.getInstance(type, ibmZSecurityProvider);
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: KeyStore.getInstance('" + type + "', IBMZSecurity) succeeded.");
- try {
- System.out.println("DEBUG [Stores] openKeyringStream: Loading class com.ibm.crypto.zsecurity.provider.RACFInputStream...");
- Class> racfClass = Class.forName("com.ibm.crypto.zsecurity.provider.RACFInputStream");
- System.out.println("DEBUG [Stores] openKeyringStream: RACFInputStream class loaded (classLoader=" + racfClass.getClassLoader() + ")");
- Constructor> ctor = racfClass.getConstructor(String.class, String.class, char[].class);
- System.out.println("DEBUG [Stores] openKeyringStream: Invoking RACFInputStream('" + userId + "', '" + ringName + "', )...");
- InputStream is = (InputStream) ctor.newInstance(userId, ringName, password);
- System.out.println("DEBUG [Stores] openKeyringStream: RACFInputStream SUCCEEDED (stream class=" + is.getClass().getName() + ")");
- return is;
- } catch (ClassNotFoundException cnfe) {
- System.err.println("DEBUG [Stores] openKeyringStream: RACFInputStream class NOT FOUND: " + cnfe.getMessage());
- throw new IOException("Cannot open keyring '" + uri + "': URL protocol handler not available " +
- "and RACFInputStream class not found. Ensure running on z/OS with IBM JDK.", cnfe);
- } catch (Exception e) {
- System.err.println("DEBUG [Stores] openKeyringStream: RACFInputStream FAILED: " + e.getClass().getName() + ": " + e.getMessage());
- e.printStackTrace(System.err);
- throw new IOException("Cannot open keyring '" + uri + "': URL protocol handler not available " +
- "and RACFInputStream fallback failed: " + e.getMessage(), e);
+ // Try loading with the keyring URL as the password
+ // Some IBM implementations accept safkeyring://userId/ring as load parameter
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3a - load(null, ringUrl as password)...");
+ try {
+ ks.load(null, formatted.toCharArray());
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3a SUCCEEDED. Aliases count=" + ks.size());
+ return ks;
+ } catch (Exception e3a) {
+ System.err.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3a FAILED: " + e3a.getClass().getName() + ": " + e3a.getMessage());
+ }
+
+ // Try loading with a DomainLoadStoreParameter-style approach
+ // Pass the ring name in format "userId/ringName" as password
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3b - load(null, userId/ringName as password)...");
+ try {
+ ks = KeyStore.getInstance(type, ibmZSecurityProvider);
+ ks.load(null, (userId + "/" + ringName).toCharArray());
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3b SUCCEEDED. Aliases count=" + ks.size());
+ return ks;
+ } catch (Exception e3b) {
+ System.err.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3b FAILED: " + e3b.getClass().getName() + ": " + e3b.getMessage());
+ }
+
+ // Try with standard password and null stream
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3c - load(null, 'password')...");
+ try {
+ ks = KeyStore.getInstance(type, ibmZSecurityProvider);
+ ks.load(null, password);
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3c SUCCEEDED. Aliases count=" + ks.size());
+ if (ks.size() > 0) {
+ return ks;
+ }
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3c returned empty keystore, not useful.");
+ } catch (Exception e3c) {
+ System.err.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3c FAILED: " + e3c.getClass().getName() + ": " + e3c.getMessage());
+ }
+ } catch (Exception e3) {
+ System.err.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3 setup FAILED: " + e3.getClass().getName() + ": " + e3.getMessage());
+ e3.printStackTrace(System.err);
+ }
+ } else {
+ System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3 SKIPPED (IBMZSecurity provider not available)");
}
+
+ // All strategies failed
+ String errorMsg = "Cannot load keyring '" + uri + "': all strategies exhausted.\n"
+ + " - URL handler: safkeyring protocol handler not found in this JDK.\n"
+ + " - RACFInputStream: likely failed due to RACF authority.\n"
+ + "Ensure the user running 'zwe validate' has READ access to the keyring.\n"
+ + "RACF commands to verify/grant access:\n"
+ + " RLIST FACILITY IRR.DIGTCERT.LISTRING ALL\n"
+ + " PERMIT IRR.DIGTCERT.LISTRING CLASS(FACILITY) ID() ACCESS(READ)\n"
+ + " SETROPTS RACLIST(FACILITY) REFRESH";
+ System.err.println("DEBUG [Stores] loadKeyringKeyStore: ALL STRATEGIES FAILED.");
+ System.err.println(errorMsg);
+ throw new IOException(errorMsg);
}
}
From 06dfd59cf442b2b056af2c2f6cc5aafefbb987f2 Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Thu, 21 May 2026 13:43:31 +0530
Subject: [PATCH 14/28] Revert "further fix"
This reverts commit 2b566bd4b4a44daee58998cd7b970606b353bb1e.
---
.../src/main/java/org/zowe/apiml/Stores.java | 195 ++++++------------
1 file changed, 58 insertions(+), 137 deletions(-)
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
index 5dcc868a41..8dad9b614d 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
@@ -15,14 +15,11 @@
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
-import java.security.Provider;
-import java.security.Security;
import java.security.cert.CertificateException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -116,9 +113,12 @@ private void initTruststore() throws IOException, CertificateException, NoSuchAl
}
if (isKeyring(conf.getTrustStore())) {
System.out.println("DEBUG [Stores] initTruststore: Detected SAF keyring URI: " + conf.getTrustStore());
- System.out.println("DEBUG [Stores] initTruststore: Loading keyring truststore (multi-strategy)...");
- this.trustStore = loadKeyringKeyStore(conf.getTrustStore(), conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
- System.out.println("DEBUG [Stores] initTruststore: Truststore loaded from keyring. Aliases count=" + trustStore.size());
+ System.out.println("DEBUG [Stores] initTruststore: Opening keyring stream (URL with RACFInputStream fallback)...");
+ try (InputStream trustStoreIStream = openKeyringStream(conf.getTrustStore(), conf.getTrustStorePassword().toCharArray())) {
+ System.out.println("DEBUG [Stores] initTruststore: Stream opened. Loading keystore type=" + conf.getTrustStoreType());
+ this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
+ System.out.println("DEBUG [Stores] initTruststore: Truststore loaded from keyring. Aliases count=" + trustStore.size());
+ }
} else {
System.out.println("DEBUG [Stores] initTruststore: Loading from file: " + conf.getTrustStore());
try (InputStream trustStoreIStream = new FileInputStream(conf.getTrustStore())) {
@@ -135,8 +135,8 @@ private void initKeystore() throws IOException, CertificateException, NoSuchAlgo
}
if (isKeyring(conf.getKeyStore())) {
System.out.println("DEBUG [Stores] initKeystore: Detected SAF keyring URI: " + conf.getKeyStore());
- try {
- this.keyStore = loadKeyringKeyStore(conf.getKeyStore(), conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
+ try (InputStream keyringIStream = openKeyringStream(conf.getKeyStore(), conf.getKeyStorePassword().toCharArray())) {
+ this.keyStore = readKeyStore(keyringIStream, conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
this.trustStore = this.keyStore;
System.out.println("DEBUG [Stores] initKeystore: Keystore loaded from keyring. Aliases count=" + keyStore.size());
} catch (Exception e) {
@@ -191,152 +191,73 @@ public static URL keyRingUrl(String uri) throws MalformedURLException {
}
/**
- * Loads a KeyStore from a SAF keyring using multiple strategies in order:
- *
- * - Standard URL approach: {@code new URL("safkeyring://...").openStream()}
- * - RACFInputStream via reflection (bypasses URL handler)
- * - Direct JCERACFKS load via IBMZSecurity provider (no InputStream needed)
- *
+ * Opens an InputStream to a SAF keyring. Tries the standard URL-based approach first,
+ * then falls back to IBM's RACFInputStream (loaded via reflection) if the URL protocol
+ * handler is not available.
*/
// TODO: REMOVE debug logging after SAF keyring issue is resolved
@SuppressWarnings("squid:S3011")
- static KeyStore loadKeyringKeyStore(String uri, char[] password, String type) throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
+ static InputStream openKeyringStream(String uri, char[] password) throws IOException {
String formatted = formatKeyringUrl(uri);
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: uri='" + uri + "' formatted='" + formatted + "' type=" + type);
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: java.protocol.handler.pkgs="
+ System.out.println("DEBUG [Stores] openKeyringStream: uri='" + uri + "' formatted='" + formatted + "'");
+ System.out.println("DEBUG [Stores] openKeyringStream: java.protocol.handler.pkgs="
+ System.getProperty("java.protocol.handler.pkgs", ""));
- Matcher matcher = KEYRING_PATTERN.matcher(uri);
- if (!matcher.matches()) {
- matcher = KEYRING_PATTERN.matcher(formatted);
- }
- String userId = matcher.matches() ? matcher.group(2) : null;
- String ringName = matcher.matches() ? matcher.group(3) : null;
-
- // Strategy 1: Standard URL-based approach (same as main API ML services)
+ // Try standard URL-based approach first (same as main API ML services)
try {
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 1 - URL approach...");
+ System.out.println("DEBUG [Stores] openKeyringStream: Attempting new URL('" + formatted + "')...");
URL url = new URL(formatted);
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: URL created. protocol=" + url.getProtocol());
- try (InputStream is = url.openStream()) {
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: URL.openStream() SUCCEEDED (stream class=" + is.getClass().getName() + ")");
- KeyStore ks = KeyStore.getInstance(type);
- ks.load(is, password);
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 1 SUCCEEDED. Aliases count=" + ks.size());
- return ks;
- }
+ System.out.println("DEBUG [Stores] openKeyringStream: URL created. protocol=" + url.getProtocol()
+ + " host=" + url.getHost() + " path=" + url.getPath()
+ + " class=" + url.getClass().getName());
+ System.out.println("DEBUG [Stores] openKeyringStream: Calling url.openStream()...");
+ InputStream is = url.openStream();
+ System.out.println("DEBUG [Stores] openKeyringStream: URL.openStream() SUCCEEDED (stream class=" + is.getClass().getName() + ")");
+ return is;
} catch (MalformedURLException urlEx) {
- System.err.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 1 FAILED (MalformedURLException): " + urlEx.getMessage());
+ System.err.println("DEBUG [Stores] openKeyringStream: URL approach FAILED with MalformedURLException: " + urlEx.getMessage());
} catch (IOException ioEx) {
- System.err.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 1 FAILED (IOException on openStream): "
+ System.err.println("DEBUG [Stores] openKeyringStream: URL.openStream() FAILED with IOException: "
+ ioEx.getClass().getName() + ": " + ioEx.getMessage());
+ ioEx.printStackTrace(System.err);
+ // Re-throw IO errors from openStream — the URL was valid but the stream failed
+ throw ioEx;
}
- // Strategy 2: RACFInputStream via reflection
- if (userId != null && ringName != null) {
- try {
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 2 - RACFInputStream('" + userId + "', '" + ringName + "')...");
- Class> racfClass = Class.forName("com.ibm.crypto.zsecurity.provider.RACFInputStream");
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: RACFInputStream class loaded (classLoader=" + racfClass.getClassLoader() + ")");
- Constructor> ctor = racfClass.getConstructor(String.class, String.class, char[].class);
- InputStream is = (InputStream) ctor.newInstance(userId, ringName, password);
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: RACFInputStream created. Loading KeyStore...");
- KeyStore ks = KeyStore.getInstance(type);
- ks.load(is, password);
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 2 SUCCEEDED. Aliases count=" + ks.size());
- return ks;
- } catch (InvocationTargetException ite) {
- Throwable cause = ite.getCause();
- String causeMsg = cause != null ? cause.getMessage() : ite.getMessage();
- System.err.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 2 FAILED (RACFInputStream constructor threw): " + causeMsg);
- if (cause != null) {
- cause.printStackTrace(System.err);
- }
- // If the error is about private key access, try Strategy 3
- if (cause instanceof IOException && causeMsg != null && causeMsg.contains("private key")) {
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: Private key permission error detected."
- + " This likely means the running user does not have RACF authority to access"
- + " private keys in keyring " + userId + "/" + ringName + "."
- + " Trying Strategy 3 (direct JCERACFKS load)...");
- } else {
- // Non-private-key error — still try Strategy 3 but log the original error
- System.err.println("DEBUG [Stores] loadKeyringKeyStore: RACFInputStream failed with non-private-key error. Trying Strategy 3...");
- }
- } catch (ClassNotFoundException cnfe) {
- System.err.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 2 FAILED (class not found): " + cnfe.getMessage());
- } catch (Exception e) {
- System.err.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 2 FAILED: " + e.getClass().getName() + ": " + e.getMessage());
- e.printStackTrace(System.err);
+ // Fallback: use com.ibm.crypto.zsecurity.provider.RACFInputStream via reflection
+ // This class is available on z/OS IBM JDKs and bypasses the URL protocol handler
+ System.out.println("DEBUG [Stores] openKeyringStream: URL handler not available, attempting RACFInputStream fallback...");
+
+ Matcher matcher = KEYRING_PATTERN.matcher(uri);
+ if (!matcher.matches()) {
+ matcher = KEYRING_PATTERN.matcher(formatted);
+ if (!matcher.matches()) {
+ throw new IOException("Cannot open keyring: invalid URI format: " + uri);
}
}
- // Strategy 3: Direct JCERACFKS KeyStore load using IBMZSecurity provider
- // On IBM z/OS JDK, the JCERACFKS KeyStore SPI can load keyrings directly
- // when the keyring URL is provided as the password/loadStoreParameter.
- // This approach does NOT use RACFInputStream and may handle inaccessible
- // private keys gracefully (loading only certificates the user can access).
- Provider ibmZSecurityProvider = Security.getProvider("IBMZSecurity");
- if (ibmZSecurityProvider != null) {
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3 - Direct JCERACFKS via IBMZSecurity provider...");
- try {
- KeyStore ks = KeyStore.getInstance(type, ibmZSecurityProvider);
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: KeyStore.getInstance('" + type + "', IBMZSecurity) succeeded.");
-
- // Try loading with the keyring URL as the password
- // Some IBM implementations accept safkeyring://userId/ring as load parameter
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3a - load(null, ringUrl as password)...");
- try {
- ks.load(null, formatted.toCharArray());
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3a SUCCEEDED. Aliases count=" + ks.size());
- return ks;
- } catch (Exception e3a) {
- System.err.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3a FAILED: " + e3a.getClass().getName() + ": " + e3a.getMessage());
- }
-
- // Try loading with a DomainLoadStoreParameter-style approach
- // Pass the ring name in format "userId/ringName" as password
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3b - load(null, userId/ringName as password)...");
- try {
- ks = KeyStore.getInstance(type, ibmZSecurityProvider);
- ks.load(null, (userId + "/" + ringName).toCharArray());
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3b SUCCEEDED. Aliases count=" + ks.size());
- return ks;
- } catch (Exception e3b) {
- System.err.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3b FAILED: " + e3b.getClass().getName() + ": " + e3b.getMessage());
- }
+ String userId = matcher.group(2);
+ String ringName = matcher.group(3);
+ System.out.println("DEBUG [Stores] openKeyringStream: Parsed userId='" + userId + "' ringName='" + ringName + "'");
- // Try with standard password and null stream
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3c - load(null, 'password')...");
- try {
- ks = KeyStore.getInstance(type, ibmZSecurityProvider);
- ks.load(null, password);
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3c SUCCEEDED. Aliases count=" + ks.size());
- if (ks.size() > 0) {
- return ks;
- }
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3c returned empty keystore, not useful.");
- } catch (Exception e3c) {
- System.err.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3c FAILED: " + e3c.getClass().getName() + ": " + e3c.getMessage());
- }
- } catch (Exception e3) {
- System.err.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3 setup FAILED: " + e3.getClass().getName() + ": " + e3.getMessage());
- e3.printStackTrace(System.err);
- }
- } else {
- System.out.println("DEBUG [Stores] loadKeyringKeyStore: Strategy 3 SKIPPED (IBMZSecurity provider not available)");
+ try {
+ System.out.println("DEBUG [Stores] openKeyringStream: Loading class com.ibm.crypto.zsecurity.provider.RACFInputStream...");
+ Class> racfClass = Class.forName("com.ibm.crypto.zsecurity.provider.RACFInputStream");
+ System.out.println("DEBUG [Stores] openKeyringStream: RACFInputStream class loaded (classLoader=" + racfClass.getClassLoader() + ")");
+ Constructor> ctor = racfClass.getConstructor(String.class, String.class, char[].class);
+ System.out.println("DEBUG [Stores] openKeyringStream: Invoking RACFInputStream('" + userId + "', '" + ringName + "', )...");
+ InputStream is = (InputStream) ctor.newInstance(userId, ringName, password);
+ System.out.println("DEBUG [Stores] openKeyringStream: RACFInputStream SUCCEEDED (stream class=" + is.getClass().getName() + ")");
+ return is;
+ } catch (ClassNotFoundException cnfe) {
+ System.err.println("DEBUG [Stores] openKeyringStream: RACFInputStream class NOT FOUND: " + cnfe.getMessage());
+ throw new IOException("Cannot open keyring '" + uri + "': URL protocol handler not available " +
+ "and RACFInputStream class not found. Ensure running on z/OS with IBM JDK.", cnfe);
+ } catch (Exception e) {
+ System.err.println("DEBUG [Stores] openKeyringStream: RACFInputStream FAILED: " + e.getClass().getName() + ": " + e.getMessage());
+ e.printStackTrace(System.err);
+ throw new IOException("Cannot open keyring '" + uri + "': URL protocol handler not available " +
+ "and RACFInputStream fallback failed: " + e.getMessage(), e);
}
-
- // All strategies failed
- String errorMsg = "Cannot load keyring '" + uri + "': all strategies exhausted.\n"
- + " - URL handler: safkeyring protocol handler not found in this JDK.\n"
- + " - RACFInputStream: likely failed due to RACF authority.\n"
- + "Ensure the user running 'zwe validate' has READ access to the keyring.\n"
- + "RACF commands to verify/grant access:\n"
- + " RLIST FACILITY IRR.DIGTCERT.LISTRING ALL\n"
- + " PERMIT IRR.DIGTCERT.LISTRING CLASS(FACILITY) ID() ACCESS(READ)\n"
- + " SETROPTS RACLIST(FACILITY) REFRESH";
- System.err.println("DEBUG [Stores] loadKeyringKeyStore: ALL STRATEGIES FAILED.");
- System.err.println(errorMsg);
- throw new IOException(errorMsg);
}
}
From 2e9ed3a536e82f99b5cc592889bf274ad089a8c6 Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Thu, 21 May 2026 13:46:07 +0530
Subject: [PATCH 15/28] Revert "further fix"
This reverts commit fc8c14f65b704344d700b7ca5991b0f3a0389ca4.
---
.../src/main/java/org/zowe/apiml/Stores.java | 85 ++-----------------
.../java/org/zowe/apiml/ZosmfJwtCheck.java | 59 +------------
2 files changed, 11 insertions(+), 133 deletions(-)
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
index 8dad9b614d..4ff78fefcc 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
@@ -14,7 +14,6 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
-import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyStore;
@@ -113,9 +112,14 @@ private void initTruststore() throws IOException, CertificateException, NoSuchAl
}
if (isKeyring(conf.getTrustStore())) {
System.out.println("DEBUG [Stores] initTruststore: Detected SAF keyring URI: " + conf.getTrustStore());
- System.out.println("DEBUG [Stores] initTruststore: Opening keyring stream (URL with RACFInputStream fallback)...");
- try (InputStream trustStoreIStream = openKeyringStream(conf.getTrustStore(), conf.getTrustStorePassword().toCharArray())) {
- System.out.println("DEBUG [Stores] initTruststore: Stream opened. Loading keystore type=" + conf.getTrustStoreType());
+ String formatted = formatKeyringUrl(conf.getTrustStore());
+ System.out.println("DEBUG [Stores] initTruststore: Formatted keyring URL: " + formatted);
+ System.out.println("DEBUG [Stores] initTruststore: Calling keyRingUrl()...");
+ URL url = keyRingUrl(conf.getTrustStore());
+ System.out.println("DEBUG [Stores] initTruststore: URL object created: " + url + " (protocol=" + url.getProtocol() + ")");
+ System.out.println("DEBUG [Stores] initTruststore: Calling url.openStream()...");
+ try (InputStream trustStoreIStream = url.openStream()) {
+ System.out.println("DEBUG [Stores] initTruststore: openStream() succeeded. Loading keystore type=" + conf.getTrustStoreType());
this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
System.out.println("DEBUG [Stores] initTruststore: Truststore loaded from keyring. Aliases count=" + trustStore.size());
}
@@ -135,7 +139,7 @@ private void initKeystore() throws IOException, CertificateException, NoSuchAlgo
}
if (isKeyring(conf.getKeyStore())) {
System.out.println("DEBUG [Stores] initKeystore: Detected SAF keyring URI: " + conf.getKeyStore());
- try (InputStream keyringIStream = openKeyringStream(conf.getKeyStore(), conf.getKeyStorePassword().toCharArray())) {
+ try (InputStream keyringIStream = keyRingUrl(conf.getKeyStore()).openStream()) {
this.keyStore = readKeyStore(keyringIStream, conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
this.trustStore = this.keyStore;
System.out.println("DEBUG [Stores] initKeystore: Keystore loaded from keyring. Aliases count=" + keyStore.size());
@@ -189,75 +193,4 @@ public static URL keyRingUrl(String uri) throws MalformedURLException {
throw e;
}
}
-
- /**
- * Opens an InputStream to a SAF keyring. Tries the standard URL-based approach first,
- * then falls back to IBM's RACFInputStream (loaded via reflection) if the URL protocol
- * handler is not available.
- */
- // TODO: REMOVE debug logging after SAF keyring issue is resolved
- @SuppressWarnings("squid:S3011")
- static InputStream openKeyringStream(String uri, char[] password) throws IOException {
- String formatted = formatKeyringUrl(uri);
- System.out.println("DEBUG [Stores] openKeyringStream: uri='" + uri + "' formatted='" + formatted + "'");
- System.out.println("DEBUG [Stores] openKeyringStream: java.protocol.handler.pkgs="
- + System.getProperty("java.protocol.handler.pkgs", ""));
-
- // Try standard URL-based approach first (same as main API ML services)
- try {
- System.out.println("DEBUG [Stores] openKeyringStream: Attempting new URL('" + formatted + "')...");
- URL url = new URL(formatted);
- System.out.println("DEBUG [Stores] openKeyringStream: URL created. protocol=" + url.getProtocol()
- + " host=" + url.getHost() + " path=" + url.getPath()
- + " class=" + url.getClass().getName());
- System.out.println("DEBUG [Stores] openKeyringStream: Calling url.openStream()...");
- InputStream is = url.openStream();
- System.out.println("DEBUG [Stores] openKeyringStream: URL.openStream() SUCCEEDED (stream class=" + is.getClass().getName() + ")");
- return is;
- } catch (MalformedURLException urlEx) {
- System.err.println("DEBUG [Stores] openKeyringStream: URL approach FAILED with MalformedURLException: " + urlEx.getMessage());
- } catch (IOException ioEx) {
- System.err.println("DEBUG [Stores] openKeyringStream: URL.openStream() FAILED with IOException: "
- + ioEx.getClass().getName() + ": " + ioEx.getMessage());
- ioEx.printStackTrace(System.err);
- // Re-throw IO errors from openStream — the URL was valid but the stream failed
- throw ioEx;
- }
-
- // Fallback: use com.ibm.crypto.zsecurity.provider.RACFInputStream via reflection
- // This class is available on z/OS IBM JDKs and bypasses the URL protocol handler
- System.out.println("DEBUG [Stores] openKeyringStream: URL handler not available, attempting RACFInputStream fallback...");
-
- Matcher matcher = KEYRING_PATTERN.matcher(uri);
- if (!matcher.matches()) {
- matcher = KEYRING_PATTERN.matcher(formatted);
- if (!matcher.matches()) {
- throw new IOException("Cannot open keyring: invalid URI format: " + uri);
- }
- }
-
- String userId = matcher.group(2);
- String ringName = matcher.group(3);
- System.out.println("DEBUG [Stores] openKeyringStream: Parsed userId='" + userId + "' ringName='" + ringName + "'");
-
- try {
- System.out.println("DEBUG [Stores] openKeyringStream: Loading class com.ibm.crypto.zsecurity.provider.RACFInputStream...");
- Class> racfClass = Class.forName("com.ibm.crypto.zsecurity.provider.RACFInputStream");
- System.out.println("DEBUG [Stores] openKeyringStream: RACFInputStream class loaded (classLoader=" + racfClass.getClassLoader() + ")");
- Constructor> ctor = racfClass.getConstructor(String.class, String.class, char[].class);
- System.out.println("DEBUG [Stores] openKeyringStream: Invoking RACFInputStream('" + userId + "', '" + ringName + "', )...");
- InputStream is = (InputStream) ctor.newInstance(userId, ringName, password);
- System.out.println("DEBUG [Stores] openKeyringStream: RACFInputStream SUCCEEDED (stream class=" + is.getClass().getName() + ")");
- return is;
- } catch (ClassNotFoundException cnfe) {
- System.err.println("DEBUG [Stores] openKeyringStream: RACFInputStream class NOT FOUND: " + cnfe.getMessage());
- throw new IOException("Cannot open keyring '" + uri + "': URL protocol handler not available " +
- "and RACFInputStream class not found. Ensure running on z/OS with IBM JDK.", cnfe);
- } catch (Exception e) {
- System.err.println("DEBUG [Stores] openKeyringStream: RACFInputStream FAILED: " + e.getClass().getName() + ": " + e.getMessage());
- e.printStackTrace(System.err);
- throw new IOException("Cannot open keyring '" + uri + "': URL protocol handler not available " +
- "and RACFInputStream fallback failed: " + e.getMessage(), e);
- }
- }
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
index 45a92ed472..1144755085 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
@@ -33,13 +33,7 @@ public static int mainWithExitCode(String[] args) {
System.out.println("DEBUG [ZosmfJwtCheck] ===== zosmf-jwt-check starting =====");
System.out.println("DEBUG [ZosmfJwtCheck] Java version: " + System.getProperty("java.version"));
System.out.println("DEBUG [ZosmfJwtCheck] Java vendor: " + System.getProperty("java.vendor"));
- System.out.println("DEBUG [ZosmfJwtCheck] Java home: " + System.getProperty("java.home"));
System.out.println("DEBUG [ZosmfJwtCheck] OS name: " + System.getProperty("os.name"));
- System.out.println("DEBUG [ZosmfJwtCheck] java.class.path: " + System.getProperty("java.class.path", ""));
- System.out.println("DEBUG [ZosmfJwtCheck] java.ext.dirs: " + System.getProperty("java.ext.dirs", ""));
- System.out.println("DEBUG [ZosmfJwtCheck] Classloader chain: app=" + ZosmfJwtCheck.class.getClassLoader()
- + " parent=" + (ZosmfJwtCheck.class.getClassLoader() != null ? ZosmfJwtCheck.class.getClassLoader().getParent() : "null")
- + " threadCtx=" + Thread.currentThread().getContextClassLoader());
System.out.println("DEBUG [ZosmfJwtCheck] java.protocol.handler.pkgs (BEFORE ensureSafkeyringHandler): "
+ System.getProperty("java.protocol.handler.pkgs", ""));
@@ -143,58 +137,9 @@ static void validateConfig(ZosmfJwtCheckConf conf) {
*/
static void ensureSafkeyringHandler() {
String existing = System.getProperty("java.protocol.handler.pkgs", "");
- System.out.println("DEBUG [ZosmfJwtCheck] ensureSafkeyringHandler: current value='" + existing + "'");
if (!existing.contains("com.ibm.crypto.provider")) {
- String newValue = existing.isEmpty() ? "com.ibm.crypto.provider" : existing + "|com.ibm.crypto.provider";
- System.setProperty("java.protocol.handler.pkgs", newValue);
- System.out.println("DEBUG [ZosmfJwtCheck] ensureSafkeyringHandler: SET to '" + newValue + "'");
- } else {
- System.out.println("DEBUG [ZosmfJwtCheck] ensureSafkeyringHandler: already contains com.ibm.crypto.provider, no change");
- }
-
- // TODO: REMOVE diagnostic checks after SAF keyring issue is resolved
- // Check which IBM z/OS keyring-related classes are available on the classpath
- String[] classesToCheck = {
- "com.ibm.crypto.provider.safkeyring.Handler",
- "com.ibm.crypto.provider.safkeyringjce.Handler",
- "com.ibm.crypto.provider.safkeyringjcecca.Handler",
- "com.ibm.crypto.provider.safkeyringjceccaracfks.Handler",
- "com.ibm.crypto.provider.safkeyringjcehybrid.Handler",
- "com.ibm.crypto.zsecurity.provider.RACFInputStream",
- "com.ibm.jsse2.IBMJSSEProvider2",
- };
- for (String className : classesToCheck) {
- try {
- Class> cls = Class.forName(className);
- System.out.println("DEBUG [ZosmfJwtCheck] Class FOUND: " + className
- + " (classLoader=" + cls.getClassLoader() + ")");
- } catch (ClassNotFoundException e) {
- System.out.println("DEBUG [ZosmfJwtCheck] Class NOT FOUND: " + className);
- }
- }
-
- // List installed security providers
- System.out.println("DEBUG [ZosmfJwtCheck] Installed security providers:");
- for (java.security.Provider p : java.security.Security.getProviders()) {
- System.out.println("DEBUG [ZosmfJwtCheck] " + p.getName() + " v" + p.getVersionStr()
- + " (" + p.getClass().getName() + ")");
- }
-
- // Test URL creation inline to capture exact failure point
- System.out.println("DEBUG [ZosmfJwtCheck] Testing URL creation for 'safkeyring://test/test'...");
- try {
- java.net.URL testUrl = new java.net.URL("safkeyring://test/test");
- System.out.println("DEBUG [ZosmfJwtCheck] URL creation SUCCEEDED: " + testUrl);
- } catch (java.net.MalformedURLException e) {
- System.out.println("DEBUG [ZosmfJwtCheck] URL creation FAILED: " + e.getMessage());
- // If the handler class was FOUND above but URL creation FAILED, that confirms
- // the bootstrap classloader theory: Class.forName from our code can find it,
- // but java.net.URL (loaded by bootstrap classloader) cannot.
- System.out.println("DEBUG [ZosmfJwtCheck] URL class classLoader: " + java.net.URL.class.getClassLoader());
- System.out.println("DEBUG [ZosmfJwtCheck] This class classLoader: " + ZosmfJwtCheck.class.getClassLoader());
- System.out.println("DEBUG [ZosmfJwtCheck] If handler class was FOUND above but URL creation FAILED,");
- System.out.println("DEBUG [ZosmfJwtCheck] this indicates a classloader visibility issue.");
- System.out.println("DEBUG [ZosmfJwtCheck] The RACFInputStream fallback in openKeyringStream() will be used instead.");
+ System.setProperty("java.protocol.handler.pkgs",
+ existing.isEmpty() ? "com.ibm.crypto.provider" : existing + "|com.ibm.crypto.provider");
}
}
From b7947a109958acca6921268b9c25900c836c8ca4 Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Thu, 21 May 2026 13:49:51 +0530
Subject: [PATCH 16/28] Revert "Debug Logs"
This reverts commit 2088e48998e9bd0d0f8aae917382dc7d05957c6b.
---
.../org/zowe/apiml/HttpClientWrapper.java | 14 -----
.../org/zowe/apiml/JwkEndpointChecker.java | 19 -------
.../org/zowe/apiml/SSLContextFactory.java | 6 ---
.../src/main/java/org/zowe/apiml/Stores.java | 52 ++-----------------
.../java/org/zowe/apiml/ZosmfJwtCheck.java | 30 -----------
5 files changed, 3 insertions(+), 118 deletions(-)
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
index 8feecfa6e3..8faca04157 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
@@ -28,7 +28,6 @@
* Supports custom {@link SSLContext} and {@link HostnameVerifier} for
* STRICT, NONSTRICT, and DISABLED certificate verification modes.
*/
-// TODO: REMOVE all "DEBUG [HttpClientWrapper]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class HttpClientWrapper {
@@ -65,11 +64,6 @@ public HttpClientWrapper() {
}
public Response executeCall(URL url, Map headers) throws IOException {
- System.out.println("DEBUG [HttpClientWrapper] executeCall() URL=" + url);
- System.out.println("DEBUG [HttpClientWrapper] useHttps=" + useHttps);
- System.out.println("DEBUG [HttpClientWrapper] sslContext=" + (sslContext != null ? sslContext.getProtocol() : ""));
- System.out.println("DEBUG [HttpClientWrapper] hostnameVerifier=" + (hostnameVerifier != null ? hostnameVerifier.getClass().getName() : ""));
-
HttpURLConnection con;
if (useHttps) {
HttpsURLConnection httpsCon = (HttpsURLConnection) url.openConnection();
@@ -78,10 +72,8 @@ public Response executeCall(URL url, Map headers) throws IOExcep
httpsCon.setHostnameVerifier(hostnameVerifier);
}
con = httpsCon;
- System.out.println("DEBUG [HttpClientWrapper] HTTPS connection opened");
} else {
con = (HttpURLConnection) url.openConnection();
- System.out.println("DEBUG [HttpClientWrapper] HTTP connection opened");
}
con.setRequestMethod("GET");
@@ -94,16 +86,10 @@ public Response executeCall(URL url, Map headers) throws IOExcep
}
}
- System.out.println("DEBUG [HttpClientWrapper] Sending GET request (connectTimeout=" + CONNECT_TIMEOUT + "ms, readTimeout=" + READ_TIMEOUT + "ms)...");
try {
int responseCode = con.getResponseCode();
- System.out.println("DEBUG [HttpClientWrapper] Response code=" + responseCode);
String body = readBody(con);
- System.out.println("DEBUG [HttpClientWrapper] Response body read, length=" + (body != null ? body.length() : ""));
return new Response(responseCode, body);
- } catch (IOException e) {
- System.err.println("DEBUG [HttpClientWrapper] IOException during request: " + e.getClass().getName() + ": " + e.getMessage());
- throw e;
} finally {
con.disconnect();
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
index 1eedc7e8d3..06f5b0681a 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
@@ -24,7 +24,6 @@
* Checks z/OSMF JWK endpoint availability at {@code /jwt/ibm/api/zOSMFBuilder/jwk}.
* Interprets the HTTP response code to determine if the endpoint is functional
*/
-// TODO: REMOVE all "DEBUG [JwkEndpointChecker]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class JwkEndpointChecker {
@@ -46,19 +45,11 @@ public boolean check() {
Map headers = new HashMap<>();
headers.put(ZOSMF_CSRF_HEADER, "");
- System.out.println("DEBUG [JwkEndpointChecker] check() called");
- System.out.println("DEBUG [JwkEndpointChecker] target URL=" + urlString);
- System.out.println("DEBUG [JwkEndpointChecker] headers=" + headers);
-
try {
URL url = new URL(urlString);
System.out.println("Checking z/OSMF JWK endpoint: " + urlString);
- System.out.println("DEBUG [JwkEndpointChecker] Calling httpClient.executeCall()...");
HttpClientWrapper.Response response = httpClient.executeCall(url, headers);
-
- System.out.println("DEBUG [JwkEndpointChecker] Response received: HTTP " + response.getStatusCode());
- System.out.println("DEBUG [JwkEndpointChecker] Response body length=" + (response.getBody() != null ? response.getBody().length() : ""));
if (conf.isVerbose() && response.getBody() != null) {
System.out.println("Response body:\n" + response.getBody());
}
@@ -67,34 +58,24 @@ public boolean check() {
System.err.println("FAILURE: SSL handshake failed when connecting to " + urlString + ".");
System.err.println("Verify that the truststore contains the z/OSMF server certificate.");
System.err.println("Details: " + e.getMessage());
- System.err.println("DEBUG [JwkEndpointChecker] SSLHandshakeException stack trace:");
- e.printStackTrace(System.err);
return false;
} catch (ConnectException e) {
System.err.println("FAILURE: Cannot connect to " + conf.getZosmfHost() + ":" + conf.getZosmfPort() + ".");
System.err.println("Verify the host and port are correct and z/OSMF is running.");
System.err.println("Details: " + e.getMessage());
- System.err.println("DEBUG [JwkEndpointChecker] ConnectException stack trace:");
- e.printStackTrace(System.err);
return false;
} catch (SocketTimeoutException e) {
System.err.println("FAILURE: Connection timed out to " + conf.getZosmfHost() + ":" + conf.getZosmfPort() + ".");
System.err.println("This is commonly caused by an incorrect host/port or a firewall blocking the connection.");
System.err.println("Verify the z/OSMF host and port are correct and that no firewall is blocking access.");
- System.err.println("DEBUG [JwkEndpointChecker] SocketTimeoutException stack trace:");
- e.printStackTrace(System.err);
return false;
} catch (UnknownHostException e) {
System.err.println("FAILURE: Error when calling " + urlString + " verify hostname and port.");
System.err.println("The host '" + conf.getZosmfHost() + "' could not be resolved.");
- System.err.println("DEBUG [JwkEndpointChecker] UnknownHostException stack trace:");
- e.printStackTrace(System.err);
return false;
} catch (Exception e) {
System.err.println("FAILURE: Error when calling " + urlString + " verify hostname and port.");
System.err.println("Details: " + e.getMessage());
- System.err.println("DEBUG [JwkEndpointChecker] Exception class=" + e.getClass().getName());
- e.printStackTrace(System.err);
return false;
}
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
index c3e1b034a0..8e4f8cedaf 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
@@ -27,7 +27,6 @@
* {@link #initTrustAllSSLContext()} — trust-all mode for DISABLED verification
*
*/
-// TODO: REMOVE all "DEBUG [SSLContextFactory]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class SSLContextFactory {
@@ -49,19 +48,15 @@ public SSLContext getSslContext() {
* @return factory holding the initialized SSLContext
*/
public static SSLContextFactory initSSLContext(Stores stores) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException, CertificateException, IOException {
- System.out.println("DEBUG [SSLContextFactory] initSSLContext() called");
SSLContextFactory factory = new SSLContextFactory(stores);
- System.out.println("DEBUG [SSLContextFactory] Initializing TrustManagerFactory with trustStore (type=" + stores.getTrustStore().getType() + ", size=" + stores.getTrustStore().size() + ")");
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(stores.getTrustStore());
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
if (stores.getKeyStore() != null) {
- System.out.println("DEBUG [SSLContextFactory] Initializing KeyManagerFactory with keyStore (type=" + stores.getKeyStore().getType() + ", size=" + stores.getKeyStore().size() + ")");
keyFactory.init(stores.getKeyStore(), stores.getConf().getKeyStorePassword().toCharArray());
} else {
- System.out.println("DEBUG [SSLContextFactory] No keyStore, using empty keystore for KeyManagerFactory");
KeyStore emptyKeystore = KeyStore.getInstance(KeyStore.getDefaultType());
emptyKeystore.load(null, null);
keyFactory.init(emptyKeystore, null);
@@ -69,7 +64,6 @@ public static SSLContextFactory initSSLContext(Stores stores) throws NoSuchAlgor
factory.sslContext = SSLContext.getInstance("TLSv1.2");
factory.sslContext.init(keyFactory.getKeyManagers(), trustFactory.getTrustManagers(), new SecureRandom());
- System.out.println("DEBUG [SSLContextFactory] SSLContext created successfully (protocol=" + factory.sslContext.getProtocol() + ")");
return factory;
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
index 4ff78fefcc..9a93584375 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
@@ -27,7 +27,6 @@
* Loads Java {@link java.security.KeyStore} instances from the filesystem
* or z/OS SAF keyrings. Supports PKCS12, JKS, and {@code safkeyring://} URIs.
*/
-// TODO: REMOVE all "DEBUG [Stores]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class Stores {
@@ -70,38 +69,22 @@ public static String formatKeyringUrl(String input) {
}
void init() {
- System.out.println("DEBUG [Stores] init() called");
- System.out.println("DEBUG [Stores] trustStore path=" + conf.getTrustStore());
- System.out.println("DEBUG [Stores] trustStore type=" + conf.getTrustStoreType());
- System.out.println("DEBUG [Stores] trustStore isKeyring=" + isKeyring(conf.getTrustStore()));
- System.out.println("DEBUG [Stores] keyStore path=" + conf.getKeyStore());
- System.out.println("DEBUG [Stores] keyStore type=" + (conf.getKeyStore() != null ? conf.getKeyStoreType() : ""));
- System.out.println("DEBUG [Stores] keyStore isKeyring=" + isKeyring(conf.getKeyStore()));
- System.out.println("DEBUG [Stores] java.protocol.handler.pkgs=" + System.getProperty("java.protocol.handler.pkgs", ""));
try {
- System.out.println("DEBUG [Stores] Calling initKeystore()...");
initKeystore();
- System.out.println("DEBUG [Stores] initKeystore() completed. trustStore set by keystore=" + (trustStore != null));
if (trustStore == null) {
- System.out.println("DEBUG [Stores] Calling initTruststore()...");
initTruststore();
- System.out.println("DEBUG [Stores] initTruststore() completed successfully.");
}
} catch (FileNotFoundException e) {
- System.err.println("DEBUG [Stores] FileNotFoundException in init(): " + e.getMessage());
- e.printStackTrace(System.err);
throw new StoresNotInitializeException("Error while loading keystore file. Error message: " + e.getMessage() + "\n" +
"Possible solution: Verify correct path to the keystore. Change owner or permission to the keystore file.");
} catch (Exception e) {
- System.err.println("DEBUG [Stores] Exception in init(): " + e.getClass().getName() + ": " + e.getMessage());
- e.printStackTrace(System.err);
throw new StoresNotInitializeException(e.getMessage());
}
}
private void initTruststore() throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
if (conf.getTrustStore() == null) {
- System.out.println("DEBUG [Stores] initTruststore: No truststore specified, will use empty.");
+ System.out.println("No truststore specified, will use empty.");
try {
this.trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
this.trustStore.load(null, null);
@@ -111,48 +94,30 @@ private void initTruststore() throws IOException, CertificateException, NoSuchAl
return;
}
if (isKeyring(conf.getTrustStore())) {
- System.out.println("DEBUG [Stores] initTruststore: Detected SAF keyring URI: " + conf.getTrustStore());
- String formatted = formatKeyringUrl(conf.getTrustStore());
- System.out.println("DEBUG [Stores] initTruststore: Formatted keyring URL: " + formatted);
- System.out.println("DEBUG [Stores] initTruststore: Calling keyRingUrl()...");
- URL url = keyRingUrl(conf.getTrustStore());
- System.out.println("DEBUG [Stores] initTruststore: URL object created: " + url + " (protocol=" + url.getProtocol() + ")");
- System.out.println("DEBUG [Stores] initTruststore: Calling url.openStream()...");
- try (InputStream trustStoreIStream = url.openStream()) {
- System.out.println("DEBUG [Stores] initTruststore: openStream() succeeded. Loading keystore type=" + conf.getTrustStoreType());
+ try (InputStream trustStoreIStream = keyRingUrl(conf.getTrustStore()).openStream()) {
this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
- System.out.println("DEBUG [Stores] initTruststore: Truststore loaded from keyring. Aliases count=" + trustStore.size());
}
} else {
- System.out.println("DEBUG [Stores] initTruststore: Loading from file: " + conf.getTrustStore());
try (InputStream trustStoreIStream = new FileInputStream(conf.getTrustStore())) {
this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
- System.out.println("DEBUG [Stores] initTruststore: Truststore loaded from file. Aliases count=" + trustStore.size());
}
}
}
private void initKeystore() throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
if (conf.getKeyStore() == null) {
- System.out.println("DEBUG [Stores] initKeystore: No keystore specified, skipping.");
return;
}
if (isKeyring(conf.getKeyStore())) {
- System.out.println("DEBUG [Stores] initKeystore: Detected SAF keyring URI: " + conf.getKeyStore());
try (InputStream keyringIStream = keyRingUrl(conf.getKeyStore()).openStream()) {
this.keyStore = readKeyStore(keyringIStream, conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
this.trustStore = this.keyStore;
- System.out.println("DEBUG [Stores] initKeystore: Keystore loaded from keyring. Aliases count=" + keyStore.size());
} catch (Exception e) {
- System.err.println("DEBUG [Stores] initKeystore: Exception loading keyring: " + e.getClass().getName() + ": " + e.getMessage());
- e.printStackTrace(System.err);
throw new StoresNotInitializeException(e.getMessage());
}
} else {
- System.out.println("DEBUG [Stores] initKeystore: Loading from file: " + conf.getKeyStore());
try (InputStream keyStoreIStream = new FileInputStream(conf.getKeyStore())) {
this.keyStore = readKeyStore(keyStoreIStream, conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
- System.out.println("DEBUG [Stores] initKeystore: Keystore loaded from file. Aliases count=" + keyStore.size());
}
}
}
@@ -180,17 +145,6 @@ public static URL keyRingUrl(String uri) throws MalformedURLException {
throw new StoresNotInitializeException("Incorrect key ring format: " + uri
+ ". Make sure you use format safkeyring://userId/keyRing");
}
- String formatted = formatKeyringUrl(uri);
- System.out.println("DEBUG [Stores] keyRingUrl: Creating URL from: " + formatted);
- try {
- URL url = new URL(formatted);
- System.out.println("DEBUG [Stores] keyRingUrl: URL created successfully. protocol=" + url.getProtocol()
- + " host=" + url.getHost() + " path=" + url.getPath());
- return url;
- } catch (MalformedURLException e) {
- System.err.println("DEBUG [Stores] keyRingUrl: MalformedURLException for '" + formatted + "': " + e.getMessage());
- System.err.println("DEBUG [Stores] keyRingUrl: java.protocol.handler.pkgs=" + System.getProperty("java.protocol.handler.pkgs", ""));
- throw e;
- }
+ return new URL(formatKeyringUrl(uri));
}
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
index 1144755085..e95093a748 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
@@ -21,7 +21,6 @@
*
* Exit codes: 0 = success, 4 = failure/error, 8 = help displayed.
*/
-// TODO: REMOVE all "DEBUG [ZosmfJwtCheck]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class ZosmfJwtCheck {
@@ -30,18 +29,7 @@ public class ZosmfJwtCheck {
static final String VERIFY_DISABLED = "DISABLED";
public static int mainWithExitCode(String[] args) {
- System.out.println("DEBUG [ZosmfJwtCheck] ===== zosmf-jwt-check starting =====");
- System.out.println("DEBUG [ZosmfJwtCheck] Java version: " + System.getProperty("java.version"));
- System.out.println("DEBUG [ZosmfJwtCheck] Java vendor: " + System.getProperty("java.vendor"));
- System.out.println("DEBUG [ZosmfJwtCheck] OS name: " + System.getProperty("os.name"));
- System.out.println("DEBUG [ZosmfJwtCheck] java.protocol.handler.pkgs (BEFORE ensureSafkeyringHandler): "
- + System.getProperty("java.protocol.handler.pkgs", ""));
-
ensureSafkeyringHandler();
-
- System.out.println("DEBUG [ZosmfJwtCheck] java.protocol.handler.pkgs (AFTER ensureSafkeyringHandler): "
- + System.getProperty("java.protocol.handler.pkgs", ""));
-
try {
ZosmfJwtCheckConf conf = new ZosmfJwtCheckConf();
CommandLine cmd = new CommandLine(conf);
@@ -53,17 +41,6 @@ public static int mainWithExitCode(String[] args) {
return 8;
}
- System.out.println("DEBUG [ZosmfJwtCheck] Parsed config:");
- System.out.println("DEBUG [ZosmfJwtCheck] zosmf-host=" + conf.getZosmfHost());
- System.out.println("DEBUG [ZosmfJwtCheck] zosmf-port=" + conf.getZosmfPort());
- System.out.println("DEBUG [ZosmfJwtCheck] scheme=" + conf.getScheme());
- System.out.println("DEBUG [ZosmfJwtCheck] verify-certificates=" + conf.getVerifyCertificates());
- System.out.println("DEBUG [ZosmfJwtCheck] truststore-file=" + conf.getTrustStore());
- System.out.println("DEBUG [ZosmfJwtCheck] truststore-type=" + conf.getTrustStoreType());
- System.out.println("DEBUG [ZosmfJwtCheck] truststore-isKeyring=" + Stores.isKeyring(conf.getTrustStore()));
- System.out.println("DEBUG [ZosmfJwtCheck] keystore-file=" + conf.getKeyStore());
- System.out.println("DEBUG [ZosmfJwtCheck] keystore-type=" + conf.getKeyStoreType());
-
validateConfig(conf);
HttpClientWrapper httpClient;
@@ -75,11 +52,8 @@ public static int mainWithExitCode(String[] args) {
HostnameVerifier noopVerifier = (hostname, session) -> true;
httpClient = new HttpClientWrapper(sslContextFactory.getSslContext(), noopVerifier);
} else {
- System.out.println("DEBUG [ZosmfJwtCheck] Creating Stores (truststore/keystore)...");
Stores stores = new Stores(conf);
- System.out.println("DEBUG [ZosmfJwtCheck] Stores created successfully. Building SSLContext...");
SSLContextFactory sslContextFactory = SSLContextFactory.initSSLContext(stores);
- System.out.println("DEBUG [ZosmfJwtCheck] SSLContext created successfully.");
HostnameVerifier hostnameVerifier;
if (VERIFY_NONSTRICT.equals(verifyMode)) {
@@ -95,14 +69,10 @@ public static int mainWithExitCode(String[] args) {
}
JwkEndpointChecker checker = new JwkEndpointChecker(httpClient, conf);
- System.out.println("DEBUG [ZosmfJwtCheck] Calling JwkEndpointChecker.check()...");
boolean success = checker.check();
- System.out.println("DEBUG [ZosmfJwtCheck] JwkEndpointChecker.check() returned: " + success);
return success ? 0 : 4;
} catch (Exception e) {
System.err.println("ERROR: " + e.getMessage());
- System.err.println("DEBUG [ZosmfJwtCheck] Exception class: " + e.getClass().getName());
- e.printStackTrace(System.err);
return 4;
}
}
From df3f10f2dbff82e0af8b9584c2c74255982122b1 Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Thu, 21 May 2026 13:57:48 +0530
Subject: [PATCH 17/28] Revert "safkeyring truststore bug fix"
This reverts commit e2d0058a2890b35ff983176e1cbf528ab1e4de0e.
---
.../main/java/org/zowe/apiml/ZosmfJwtCheck.java | 15 ---------------
1 file changed, 15 deletions(-)
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
index e95093a748..bb13cc57bc 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
@@ -29,7 +29,6 @@ public class ZosmfJwtCheck {
static final String VERIFY_DISABLED = "DISABLED";
public static int mainWithExitCode(String[] args) {
- ensureSafkeyringHandler();
try {
ZosmfJwtCheckConf conf = new ZosmfJwtCheckConf();
CommandLine cmd = new CommandLine(conf);
@@ -99,20 +98,6 @@ static void validateConfig(ZosmfJwtCheckConf conf) {
}
}
- /**
- * Registers the IBM SAF keyring URL protocol handler so that
- * {@code new URL("safkeyring://...")} works on z/OS without requiring the
- * caller to pass {@code -Djava.protocol.handler.pkgs=com.ibm.crypto.provider}.
- * On non-z/OS platforms the handler class is simply not found and is ignored.
- */
- static void ensureSafkeyringHandler() {
- String existing = System.getProperty("java.protocol.handler.pkgs", "");
- if (!existing.contains("com.ibm.crypto.provider")) {
- System.setProperty("java.protocol.handler.pkgs",
- existing.isEmpty() ? "com.ibm.crypto.provider" : existing + "|com.ibm.crypto.provider");
- }
- }
-
public static void main(String[] args) {
System.exit(mainWithExitCode(args));
}
From 4ed5b49a9622e8c839a9d6983a5bfd2e9ebb5933 Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Mon, 25 May 2026 10:28:32 +0530
Subject: [PATCH 18/28] debug logs for safkeyring issue
Signed-off-by: hrishikesh-nalawade
---
.../org/zowe/apiml/HttpClientWrapper.java | 14 +++++
.../org/zowe/apiml/JwkEndpointChecker.java | 19 +++++++
.../org/zowe/apiml/SSLContextFactory.java | 6 +++
.../src/main/java/org/zowe/apiml/Stores.java | 52 +++++++++++++++++--
.../java/org/zowe/apiml/ZosmfJwtCheck.java | 45 ++++++++++++++++
5 files changed, 133 insertions(+), 3 deletions(-)
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
index 8faca04157..8feecfa6e3 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
@@ -28,6 +28,7 @@
* Supports custom {@link SSLContext} and {@link HostnameVerifier} for
* STRICT, NONSTRICT, and DISABLED certificate verification modes.
*/
+// TODO: REMOVE all "DEBUG [HttpClientWrapper]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class HttpClientWrapper {
@@ -64,6 +65,11 @@ public HttpClientWrapper() {
}
public Response executeCall(URL url, Map headers) throws IOException {
+ System.out.println("DEBUG [HttpClientWrapper] executeCall() URL=" + url);
+ System.out.println("DEBUG [HttpClientWrapper] useHttps=" + useHttps);
+ System.out.println("DEBUG [HttpClientWrapper] sslContext=" + (sslContext != null ? sslContext.getProtocol() : ""));
+ System.out.println("DEBUG [HttpClientWrapper] hostnameVerifier=" + (hostnameVerifier != null ? hostnameVerifier.getClass().getName() : ""));
+
HttpURLConnection con;
if (useHttps) {
HttpsURLConnection httpsCon = (HttpsURLConnection) url.openConnection();
@@ -72,8 +78,10 @@ public Response executeCall(URL url, Map headers) throws IOExcep
httpsCon.setHostnameVerifier(hostnameVerifier);
}
con = httpsCon;
+ System.out.println("DEBUG [HttpClientWrapper] HTTPS connection opened");
} else {
con = (HttpURLConnection) url.openConnection();
+ System.out.println("DEBUG [HttpClientWrapper] HTTP connection opened");
}
con.setRequestMethod("GET");
@@ -86,10 +94,16 @@ public Response executeCall(URL url, Map headers) throws IOExcep
}
}
+ System.out.println("DEBUG [HttpClientWrapper] Sending GET request (connectTimeout=" + CONNECT_TIMEOUT + "ms, readTimeout=" + READ_TIMEOUT + "ms)...");
try {
int responseCode = con.getResponseCode();
+ System.out.println("DEBUG [HttpClientWrapper] Response code=" + responseCode);
String body = readBody(con);
+ System.out.println("DEBUG [HttpClientWrapper] Response body read, length=" + (body != null ? body.length() : ""));
return new Response(responseCode, body);
+ } catch (IOException e) {
+ System.err.println("DEBUG [HttpClientWrapper] IOException during request: " + e.getClass().getName() + ": " + e.getMessage());
+ throw e;
} finally {
con.disconnect();
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
index 06f5b0681a..1eedc7e8d3 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
@@ -24,6 +24,7 @@
* Checks z/OSMF JWK endpoint availability at {@code /jwt/ibm/api/zOSMFBuilder/jwk}.
* Interprets the HTTP response code to determine if the endpoint is functional
*/
+// TODO: REMOVE all "DEBUG [JwkEndpointChecker]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class JwkEndpointChecker {
@@ -45,11 +46,19 @@ public boolean check() {
Map headers = new HashMap<>();
headers.put(ZOSMF_CSRF_HEADER, "");
+ System.out.println("DEBUG [JwkEndpointChecker] check() called");
+ System.out.println("DEBUG [JwkEndpointChecker] target URL=" + urlString);
+ System.out.println("DEBUG [JwkEndpointChecker] headers=" + headers);
+
try {
URL url = new URL(urlString);
System.out.println("Checking z/OSMF JWK endpoint: " + urlString);
+ System.out.println("DEBUG [JwkEndpointChecker] Calling httpClient.executeCall()...");
HttpClientWrapper.Response response = httpClient.executeCall(url, headers);
+
+ System.out.println("DEBUG [JwkEndpointChecker] Response received: HTTP " + response.getStatusCode());
+ System.out.println("DEBUG [JwkEndpointChecker] Response body length=" + (response.getBody() != null ? response.getBody().length() : ""));
if (conf.isVerbose() && response.getBody() != null) {
System.out.println("Response body:\n" + response.getBody());
}
@@ -58,24 +67,34 @@ public boolean check() {
System.err.println("FAILURE: SSL handshake failed when connecting to " + urlString + ".");
System.err.println("Verify that the truststore contains the z/OSMF server certificate.");
System.err.println("Details: " + e.getMessage());
+ System.err.println("DEBUG [JwkEndpointChecker] SSLHandshakeException stack trace:");
+ e.printStackTrace(System.err);
return false;
} catch (ConnectException e) {
System.err.println("FAILURE: Cannot connect to " + conf.getZosmfHost() + ":" + conf.getZosmfPort() + ".");
System.err.println("Verify the host and port are correct and z/OSMF is running.");
System.err.println("Details: " + e.getMessage());
+ System.err.println("DEBUG [JwkEndpointChecker] ConnectException stack trace:");
+ e.printStackTrace(System.err);
return false;
} catch (SocketTimeoutException e) {
System.err.println("FAILURE: Connection timed out to " + conf.getZosmfHost() + ":" + conf.getZosmfPort() + ".");
System.err.println("This is commonly caused by an incorrect host/port or a firewall blocking the connection.");
System.err.println("Verify the z/OSMF host and port are correct and that no firewall is blocking access.");
+ System.err.println("DEBUG [JwkEndpointChecker] SocketTimeoutException stack trace:");
+ e.printStackTrace(System.err);
return false;
} catch (UnknownHostException e) {
System.err.println("FAILURE: Error when calling " + urlString + " verify hostname and port.");
System.err.println("The host '" + conf.getZosmfHost() + "' could not be resolved.");
+ System.err.println("DEBUG [JwkEndpointChecker] UnknownHostException stack trace:");
+ e.printStackTrace(System.err);
return false;
} catch (Exception e) {
System.err.println("FAILURE: Error when calling " + urlString + " verify hostname and port.");
System.err.println("Details: " + e.getMessage());
+ System.err.println("DEBUG [JwkEndpointChecker] Exception class=" + e.getClass().getName());
+ e.printStackTrace(System.err);
return false;
}
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
index 8e4f8cedaf..c3e1b034a0 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
@@ -27,6 +27,7 @@
* {@link #initTrustAllSSLContext()} — trust-all mode for DISABLED verification
*
*/
+// TODO: REMOVE all "DEBUG [SSLContextFactory]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class SSLContextFactory {
@@ -48,15 +49,19 @@ public SSLContext getSslContext() {
* @return factory holding the initialized SSLContext
*/
public static SSLContextFactory initSSLContext(Stores stores) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException, CertificateException, IOException {
+ System.out.println("DEBUG [SSLContextFactory] initSSLContext() called");
SSLContextFactory factory = new SSLContextFactory(stores);
+ System.out.println("DEBUG [SSLContextFactory] Initializing TrustManagerFactory with trustStore (type=" + stores.getTrustStore().getType() + ", size=" + stores.getTrustStore().size() + ")");
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(stores.getTrustStore());
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
if (stores.getKeyStore() != null) {
+ System.out.println("DEBUG [SSLContextFactory] Initializing KeyManagerFactory with keyStore (type=" + stores.getKeyStore().getType() + ", size=" + stores.getKeyStore().size() + ")");
keyFactory.init(stores.getKeyStore(), stores.getConf().getKeyStorePassword().toCharArray());
} else {
+ System.out.println("DEBUG [SSLContextFactory] No keyStore, using empty keystore for KeyManagerFactory");
KeyStore emptyKeystore = KeyStore.getInstance(KeyStore.getDefaultType());
emptyKeystore.load(null, null);
keyFactory.init(emptyKeystore, null);
@@ -64,6 +69,7 @@ public static SSLContextFactory initSSLContext(Stores stores) throws NoSuchAlgor
factory.sslContext = SSLContext.getInstance("TLSv1.2");
factory.sslContext.init(keyFactory.getKeyManagers(), trustFactory.getTrustManagers(), new SecureRandom());
+ System.out.println("DEBUG [SSLContextFactory] SSLContext created successfully (protocol=" + factory.sslContext.getProtocol() + ")");
return factory;
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
index 9a93584375..4ff78fefcc 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
@@ -27,6 +27,7 @@
* Loads Java {@link java.security.KeyStore} instances from the filesystem
* or z/OS SAF keyrings. Supports PKCS12, JKS, and {@code safkeyring://} URIs.
*/
+// TODO: REMOVE all "DEBUG [Stores]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class Stores {
@@ -69,22 +70,38 @@ public static String formatKeyringUrl(String input) {
}
void init() {
+ System.out.println("DEBUG [Stores] init() called");
+ System.out.println("DEBUG [Stores] trustStore path=" + conf.getTrustStore());
+ System.out.println("DEBUG [Stores] trustStore type=" + conf.getTrustStoreType());
+ System.out.println("DEBUG [Stores] trustStore isKeyring=" + isKeyring(conf.getTrustStore()));
+ System.out.println("DEBUG [Stores] keyStore path=" + conf.getKeyStore());
+ System.out.println("DEBUG [Stores] keyStore type=" + (conf.getKeyStore() != null ? conf.getKeyStoreType() : ""));
+ System.out.println("DEBUG [Stores] keyStore isKeyring=" + isKeyring(conf.getKeyStore()));
+ System.out.println("DEBUG [Stores] java.protocol.handler.pkgs=" + System.getProperty("java.protocol.handler.pkgs", ""));
try {
+ System.out.println("DEBUG [Stores] Calling initKeystore()...");
initKeystore();
+ System.out.println("DEBUG [Stores] initKeystore() completed. trustStore set by keystore=" + (trustStore != null));
if (trustStore == null) {
+ System.out.println("DEBUG [Stores] Calling initTruststore()...");
initTruststore();
+ System.out.println("DEBUG [Stores] initTruststore() completed successfully.");
}
} catch (FileNotFoundException e) {
+ System.err.println("DEBUG [Stores] FileNotFoundException in init(): " + e.getMessage());
+ e.printStackTrace(System.err);
throw new StoresNotInitializeException("Error while loading keystore file. Error message: " + e.getMessage() + "\n" +
"Possible solution: Verify correct path to the keystore. Change owner or permission to the keystore file.");
} catch (Exception e) {
+ System.err.println("DEBUG [Stores] Exception in init(): " + e.getClass().getName() + ": " + e.getMessage());
+ e.printStackTrace(System.err);
throw new StoresNotInitializeException(e.getMessage());
}
}
private void initTruststore() throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
if (conf.getTrustStore() == null) {
- System.out.println("No truststore specified, will use empty.");
+ System.out.println("DEBUG [Stores] initTruststore: No truststore specified, will use empty.");
try {
this.trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
this.trustStore.load(null, null);
@@ -94,30 +111,48 @@ private void initTruststore() throws IOException, CertificateException, NoSuchAl
return;
}
if (isKeyring(conf.getTrustStore())) {
- try (InputStream trustStoreIStream = keyRingUrl(conf.getTrustStore()).openStream()) {
+ System.out.println("DEBUG [Stores] initTruststore: Detected SAF keyring URI: " + conf.getTrustStore());
+ String formatted = formatKeyringUrl(conf.getTrustStore());
+ System.out.println("DEBUG [Stores] initTruststore: Formatted keyring URL: " + formatted);
+ System.out.println("DEBUG [Stores] initTruststore: Calling keyRingUrl()...");
+ URL url = keyRingUrl(conf.getTrustStore());
+ System.out.println("DEBUG [Stores] initTruststore: URL object created: " + url + " (protocol=" + url.getProtocol() + ")");
+ System.out.println("DEBUG [Stores] initTruststore: Calling url.openStream()...");
+ try (InputStream trustStoreIStream = url.openStream()) {
+ System.out.println("DEBUG [Stores] initTruststore: openStream() succeeded. Loading keystore type=" + conf.getTrustStoreType());
this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
+ System.out.println("DEBUG [Stores] initTruststore: Truststore loaded from keyring. Aliases count=" + trustStore.size());
}
} else {
+ System.out.println("DEBUG [Stores] initTruststore: Loading from file: " + conf.getTrustStore());
try (InputStream trustStoreIStream = new FileInputStream(conf.getTrustStore())) {
this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
+ System.out.println("DEBUG [Stores] initTruststore: Truststore loaded from file. Aliases count=" + trustStore.size());
}
}
}
private void initKeystore() throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
if (conf.getKeyStore() == null) {
+ System.out.println("DEBUG [Stores] initKeystore: No keystore specified, skipping.");
return;
}
if (isKeyring(conf.getKeyStore())) {
+ System.out.println("DEBUG [Stores] initKeystore: Detected SAF keyring URI: " + conf.getKeyStore());
try (InputStream keyringIStream = keyRingUrl(conf.getKeyStore()).openStream()) {
this.keyStore = readKeyStore(keyringIStream, conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
this.trustStore = this.keyStore;
+ System.out.println("DEBUG [Stores] initKeystore: Keystore loaded from keyring. Aliases count=" + keyStore.size());
} catch (Exception e) {
+ System.err.println("DEBUG [Stores] initKeystore: Exception loading keyring: " + e.getClass().getName() + ": " + e.getMessage());
+ e.printStackTrace(System.err);
throw new StoresNotInitializeException(e.getMessage());
}
} else {
+ System.out.println("DEBUG [Stores] initKeystore: Loading from file: " + conf.getKeyStore());
try (InputStream keyStoreIStream = new FileInputStream(conf.getKeyStore())) {
this.keyStore = readKeyStore(keyStoreIStream, conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
+ System.out.println("DEBUG [Stores] initKeystore: Keystore loaded from file. Aliases count=" + keyStore.size());
}
}
}
@@ -145,6 +180,17 @@ public static URL keyRingUrl(String uri) throws MalformedURLException {
throw new StoresNotInitializeException("Incorrect key ring format: " + uri
+ ". Make sure you use format safkeyring://userId/keyRing");
}
- return new URL(formatKeyringUrl(uri));
+ String formatted = formatKeyringUrl(uri);
+ System.out.println("DEBUG [Stores] keyRingUrl: Creating URL from: " + formatted);
+ try {
+ URL url = new URL(formatted);
+ System.out.println("DEBUG [Stores] keyRingUrl: URL created successfully. protocol=" + url.getProtocol()
+ + " host=" + url.getHost() + " path=" + url.getPath());
+ return url;
+ } catch (MalformedURLException e) {
+ System.err.println("DEBUG [Stores] keyRingUrl: MalformedURLException for '" + formatted + "': " + e.getMessage());
+ System.err.println("DEBUG [Stores] keyRingUrl: java.protocol.handler.pkgs=" + System.getProperty("java.protocol.handler.pkgs", ""));
+ throw e;
+ }
}
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
index bb13cc57bc..1144755085 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
@@ -21,6 +21,7 @@
*
* Exit codes: 0 = success, 4 = failure/error, 8 = help displayed.
*/
+// TODO: REMOVE all "DEBUG [ZosmfJwtCheck]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class ZosmfJwtCheck {
@@ -29,6 +30,18 @@ public class ZosmfJwtCheck {
static final String VERIFY_DISABLED = "DISABLED";
public static int mainWithExitCode(String[] args) {
+ System.out.println("DEBUG [ZosmfJwtCheck] ===== zosmf-jwt-check starting =====");
+ System.out.println("DEBUG [ZosmfJwtCheck] Java version: " + System.getProperty("java.version"));
+ System.out.println("DEBUG [ZosmfJwtCheck] Java vendor: " + System.getProperty("java.vendor"));
+ System.out.println("DEBUG [ZosmfJwtCheck] OS name: " + System.getProperty("os.name"));
+ System.out.println("DEBUG [ZosmfJwtCheck] java.protocol.handler.pkgs (BEFORE ensureSafkeyringHandler): "
+ + System.getProperty("java.protocol.handler.pkgs", ""));
+
+ ensureSafkeyringHandler();
+
+ System.out.println("DEBUG [ZosmfJwtCheck] java.protocol.handler.pkgs (AFTER ensureSafkeyringHandler): "
+ + System.getProperty("java.protocol.handler.pkgs", ""));
+
try {
ZosmfJwtCheckConf conf = new ZosmfJwtCheckConf();
CommandLine cmd = new CommandLine(conf);
@@ -40,6 +53,17 @@ public static int mainWithExitCode(String[] args) {
return 8;
}
+ System.out.println("DEBUG [ZosmfJwtCheck] Parsed config:");
+ System.out.println("DEBUG [ZosmfJwtCheck] zosmf-host=" + conf.getZosmfHost());
+ System.out.println("DEBUG [ZosmfJwtCheck] zosmf-port=" + conf.getZosmfPort());
+ System.out.println("DEBUG [ZosmfJwtCheck] scheme=" + conf.getScheme());
+ System.out.println("DEBUG [ZosmfJwtCheck] verify-certificates=" + conf.getVerifyCertificates());
+ System.out.println("DEBUG [ZosmfJwtCheck] truststore-file=" + conf.getTrustStore());
+ System.out.println("DEBUG [ZosmfJwtCheck] truststore-type=" + conf.getTrustStoreType());
+ System.out.println("DEBUG [ZosmfJwtCheck] truststore-isKeyring=" + Stores.isKeyring(conf.getTrustStore()));
+ System.out.println("DEBUG [ZosmfJwtCheck] keystore-file=" + conf.getKeyStore());
+ System.out.println("DEBUG [ZosmfJwtCheck] keystore-type=" + conf.getKeyStoreType());
+
validateConfig(conf);
HttpClientWrapper httpClient;
@@ -51,8 +75,11 @@ public static int mainWithExitCode(String[] args) {
HostnameVerifier noopVerifier = (hostname, session) -> true;
httpClient = new HttpClientWrapper(sslContextFactory.getSslContext(), noopVerifier);
} else {
+ System.out.println("DEBUG [ZosmfJwtCheck] Creating Stores (truststore/keystore)...");
Stores stores = new Stores(conf);
+ System.out.println("DEBUG [ZosmfJwtCheck] Stores created successfully. Building SSLContext...");
SSLContextFactory sslContextFactory = SSLContextFactory.initSSLContext(stores);
+ System.out.println("DEBUG [ZosmfJwtCheck] SSLContext created successfully.");
HostnameVerifier hostnameVerifier;
if (VERIFY_NONSTRICT.equals(verifyMode)) {
@@ -68,10 +95,14 @@ public static int mainWithExitCode(String[] args) {
}
JwkEndpointChecker checker = new JwkEndpointChecker(httpClient, conf);
+ System.out.println("DEBUG [ZosmfJwtCheck] Calling JwkEndpointChecker.check()...");
boolean success = checker.check();
+ System.out.println("DEBUG [ZosmfJwtCheck] JwkEndpointChecker.check() returned: " + success);
return success ? 0 : 4;
} catch (Exception e) {
System.err.println("ERROR: " + e.getMessage());
+ System.err.println("DEBUG [ZosmfJwtCheck] Exception class: " + e.getClass().getName());
+ e.printStackTrace(System.err);
return 4;
}
}
@@ -98,6 +129,20 @@ static void validateConfig(ZosmfJwtCheckConf conf) {
}
}
+ /**
+ * Registers the IBM SAF keyring URL protocol handler so that
+ * {@code new URL("safkeyring://...")} works on z/OS without requiring the
+ * caller to pass {@code -Djava.protocol.handler.pkgs=com.ibm.crypto.provider}.
+ * On non-z/OS platforms the handler class is simply not found and is ignored.
+ */
+ static void ensureSafkeyringHandler() {
+ String existing = System.getProperty("java.protocol.handler.pkgs", "");
+ if (!existing.contains("com.ibm.crypto.provider")) {
+ System.setProperty("java.protocol.handler.pkgs",
+ existing.isEmpty() ? "com.ibm.crypto.provider" : existing + "|com.ibm.crypto.provider");
+ }
+ }
+
public static void main(String[] args) {
System.exit(mainWithExitCode(args));
}
From ab021b3dbccef7f6aba7f91ac970a1304b441b0a Mon Sep 17 00:00:00 2001
From: "Signed-off-by: hrishikesh-nalawade"
Date: Mon, 25 May 2026 15:11:32 +0530
Subject: [PATCH 19/28] new protocol handler code
Signed-off-by: hrishikesh-nalawade
---
.../src/main/java/org/zowe/apiml/Stores.java | 99 +++++++++++++++++++
.../java/org/zowe/apiml/ZosmfJwtCheck.java | 36 +++++--
2 files changed, 128 insertions(+), 7 deletions(-)
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
index 4ff78fefcc..a47ec2ef2e 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
@@ -188,9 +188,108 @@ public static URL keyRingUrl(String uri) throws MalformedURLException {
+ " host=" + url.getHost() + " path=" + url.getPath());
return url;
} catch (MalformedURLException e) {
+ System.out.println("DEBUG [Stores] keyRingUrl: Standard URL creation failed, trying explicit handler lookup...");
+ URL fallbackUrl = createUrlWithExplicitHandler(formatted);
+ if (fallbackUrl != null) {
+ System.out.println("DEBUG [Stores] keyRingUrl: URL created via explicit handler. protocol=" + fallbackUrl.getProtocol());
+ return fallbackUrl;
+ }
System.err.println("DEBUG [Stores] keyRingUrl: MalformedURLException for '" + formatted + "': " + e.getMessage());
System.err.println("DEBUG [Stores] keyRingUrl: java.protocol.handler.pkgs=" + System.getProperty("java.protocol.handler.pkgs", ""));
throw e;
}
}
+
+ /**
+ * Fallback for IBM Java 17/21+ on z/OS where the safkeyring handler is registered
+ * via the {@code java.net.spi.URLStreamHandlerProvider} SPI mechanism in modules
+ * {@code ibm.crypto.zsecurity} and {@code ibm.crypto.hdwrcca}.
+ *
+ * If the modules are resolved (via {@code --add-modules}), the standard
+ * {@code new URL()} works. This fallback handles the case where the module IS
+ * resolved but the ServiceLoader lookup failed, or where we can reflectively
+ * instantiate the provider and obtain a handler.
+ */
+ private static URL createUrlWithExplicitHandler(String formatted) {
+ // Extract protocol from the formatted URI (e.g., "safkeyring", "safkeyringjce", etc.)
+ String protocol = formatted.substring(0, formatted.indexOf(':'));
+ System.out.println("DEBUG [Stores] createUrlWithExplicitHandler: protocol='" + protocol + "' formatted='" + formatted + "'");
+
+ // Try 1: Use the URLStreamHandlerProvider SPI classes directly (Java 17/21 on z/OS)
+ // These are the actual provider classes found in ibm.crypto.zsecurity and ibm.crypto.hdwrcca
+ String[] spiProviderClasses = {
+ "com.ibm.crypto.zsecurity.provider.safkeyring.Provider",
+ "com.ibm.crypto.hdwrCCA.provider.safkeyring.Provider"
+ };
+
+ ClassLoader[] loaders = {
+ ClassLoader.getSystemClassLoader(),
+ ClassLoader.getPlatformClassLoader(),
+ Thread.currentThread().getContextClassLoader(),
+ Stores.class.getClassLoader()
+ };
+ String[] loaderNames = {"SystemClassLoader", "PlatformClassLoader", "ContextClassLoader", "StoresClassLoader"};
+
+ System.out.println("DEBUG [Stores] createUrlWithExplicitHandler: Trying SPI providers...");
+ for (String providerClassName : spiProviderClasses) {
+ for (int i = 0; i < loaders.length; i++) {
+ ClassLoader loader = loaders[i];
+ if (loader == null) {
+ System.out.println("DEBUG [Stores] Skipping null loader: " + loaderNames[i]);
+ continue;
+ }
+ try {
+ System.out.println("DEBUG [Stores] Trying SPI class=" + providerClassName + " loader=" + loaderNames[i] + " (" + loader.getClass().getName() + ")");
+ Class> cls = Class.forName(providerClassName, true, loader);
+ System.out.println("DEBUG [Stores] Class loaded successfully: " + cls.getName());
+ java.net.spi.URLStreamHandlerProvider provider =
+ (java.net.spi.URLStreamHandlerProvider) cls.getDeclaredConstructor().newInstance();
+ System.out.println("DEBUG [Stores] Provider instantiated: " + provider.getClass().getName());
+ java.net.URLStreamHandler handler = provider.createURLStreamHandler(protocol);
+ if (handler != null) {
+ System.out.println("DEBUG [Stores] SUCCESS: Handler obtained for protocol '" + protocol + "' from " + providerClassName + " via " + loaderNames[i]);
+ return new URL(null, formatted, handler);
+ } else {
+ System.out.println("DEBUG [Stores] Provider returned null handler for protocol '" + protocol + "' (not supported by this provider)");
+ }
+ } catch (ClassNotFoundException e) {
+ System.out.println("DEBUG [Stores] ClassNotFoundException: " + providerClassName + " not found via " + loaderNames[i]);
+ } catch (Exception e) {
+ System.out.println("DEBUG [Stores] Exception: " + e.getClass().getName() + ": " + e.getMessage());
+ }
+ }
+ }
+
+ // Try 2: Legacy approach - look for Handler class directly (Java 8 style)
+ System.out.println("DEBUG [Stores] createUrlWithExplicitHandler: SPI providers not found. Trying legacy Handler classes...");
+ String[] handlerPackages = {
+ "com.ibm.crypto.provider",
+ "com.ibm.crypto.zsecurity.provider",
+ "com.ibm.crypto.hdwrCCA.provider"
+ };
+
+ for (String pkg : handlerPackages) {
+ String className = pkg + "." + protocol + ".Handler";
+ for (int i = 0; i < loaders.length; i++) {
+ ClassLoader loader = loaders[i];
+ if (loader == null) continue;
+ try {
+ System.out.println("DEBUG [Stores] Trying Handler class=" + className + " loader=" + loaderNames[i]);
+ Class> cls = Class.forName(className, true, loader);
+ java.net.URLStreamHandler handler = (java.net.URLStreamHandler) cls.getDeclaredConstructor().newInstance();
+ System.out.println("DEBUG [Stores] SUCCESS: Handler instantiated: " + className + " via " + loaderNames[i]);
+ return new URL(null, formatted, handler);
+ } catch (ClassNotFoundException e) {
+ System.out.println("DEBUG [Stores] ClassNotFoundException: " + className + " not found via " + loaderNames[i]);
+ } catch (Exception e) {
+ System.out.println("DEBUG [Stores] Exception: " + e.getClass().getName() + ": " + e.getMessage());
+ }
+ }
+ }
+
+ System.err.println("DEBUG [Stores] createUrlWithExplicitHandler: FAILED - Could not find handler for protocol '" + protocol + "' in any known location.");
+ System.err.println("DEBUG [Stores] createUrlWithExplicitHandler: Ensure the JVM is started with: --add-modules ibm.crypto.zsecurity,ibm.crypto.hdwrcca");
+ System.err.println("DEBUG [Stores] createUrlWithExplicitHandler: Resolved modules can be checked with: java --show-module-resolution --add-modules ibm.crypto.zsecurity -version");
+ return null;
+ }
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
index 1144755085..84f6380145 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
@@ -130,17 +130,39 @@ static void validateConfig(ZosmfJwtCheckConf conf) {
}
/**
- * Registers the IBM SAF keyring URL protocol handler so that
- * {@code new URL("safkeyring://...")} works on z/OS without requiring the
- * caller to pass {@code -Djava.protocol.handler.pkgs=com.ibm.crypto.provider}.
- * On non-z/OS platforms the handler class is simply not found and is ignored.
+ * Attempts to register the IBM SAF keyring URL protocol handler via the legacy
+ * {@code java.protocol.handler.pkgs} system property.
+ *
+ * Note: On IBM Java 17/21 (z/OS), the safkeyring handler is registered
+ * via the {@code java.net.spi.URLStreamHandlerProvider} SPI in module
+ * {@code ibm.crypto.zsecurity}, so this property has no effect. The real fix is
+ * to launch the JVM with {@code --add-modules ibm.crypto.zsecurity}.
+ * This method is retained only as a best-effort fallback for IBM Java 8.
*/
static void ensureSafkeyringHandler() {
+ String[] packagePrefixes = {
+ "com.ibm.crypto.zsecurity.provider",
+ "com.ibm.crypto.hdwrCCA.provider"
+ };
String existing = System.getProperty("java.protocol.handler.pkgs", "");
- if (!existing.contains("com.ibm.crypto.provider")) {
- System.setProperty("java.protocol.handler.pkgs",
- existing.isEmpty() ? "com.ibm.crypto.provider" : existing + "|com.ibm.crypto.provider");
+ System.out.println("DEBUG [ZosmfJwtCheck] ensureSafkeyringHandler: existing java.protocol.handler.pkgs='" + existing + "'");
+ StringBuilder sb = new StringBuilder(existing);
+ for (String prefix : packagePrefixes) {
+ if (!existing.contains(prefix)) {
+ if (sb.length() > 0) {
+ sb.append('|');
+ }
+ sb.append(prefix);
+ System.out.println("DEBUG [ZosmfJwtCheck] ensureSafkeyringHandler: Adding package prefix: " + prefix);
+ } else {
+ System.out.println("DEBUG [ZosmfJwtCheck] ensureSafkeyringHandler: Already present: " + prefix);
+ }
}
+ String finalValue = sb.toString();
+ System.setProperty("java.protocol.handler.pkgs", finalValue);
+ System.out.println("DEBUG [ZosmfJwtCheck] ensureSafkeyringHandler: Final java.protocol.handler.pkgs='" + finalValue + "'");
+ System.out.println("DEBUG [ZosmfJwtCheck] ensureSafkeyringHandler: NOTE - On Java 17/21, this property has no effect.");
+ System.out.println("DEBUG [ZosmfJwtCheck] ensureSafkeyringHandler: The safkeyring handler is loaded via SPI from module ibm.crypto.zsecurity (requires --add-modules).");
}
public static void main(String[] args) {
From 75ad634a12c4e9b585ad332fa7c65e3f17873f46 Mon Sep 17 00:00:00 2001
From: "Signed-off-by: hrishikesh-nalawade"
Date: Mon, 25 May 2026 17:15:15 +0530
Subject: [PATCH 20/28] reverting fallback fixes and debug logs
Signed-off-by: hrishikesh-nalawade
---
zosmf-jwt-check/README.md | 9 +-
.../org/zowe/apiml/HttpClientWrapper.java | 15 ---
.../org/zowe/apiml/JwkEndpointChecker.java | 18 ---
.../org/zowe/apiml/SSLContextFactory.java | 6 -
.../src/main/java/org/zowe/apiml/Stores.java | 110 ++----------------
.../java/org/zowe/apiml/ZosmfJwtCheck.java | 69 +----------
6 files changed, 18 insertions(+), 209 deletions(-)
diff --git a/zosmf-jwt-check/README.md b/zosmf-jwt-check/README.md
index 7536e4aa71..ae7a9cbcb0 100644
--- a/zosmf-jwt-check/README.md
+++ b/zosmf-jwt-check/README.md
@@ -304,10 +304,10 @@ java -jar zosmf-jwt-check-.jar --zosmf-host nonexistent.host --zosmf-po
## SAF Keyrings
-On z/OS, if you are using SAF keyrings instead of file-based keystores/truststores, provide the keyring path in the `safkeyring://` format and add the JVM protocol handler:
+On z/OS, if you are using SAF keyrings instead of file-based keystores/truststores, provide the keyring path in the `safkeyring://` format and add the IBM crypto modules to the JVM module graph:
```bash
-java -Djava.protocol.handler.pkgs=com.ibm.crypto.provider \
+java --add-modules ibm.crypto.zsecurity \
-jar zosmf-jwt-check-.jar \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
@@ -315,3 +315,8 @@ java -Djava.protocol.handler.pkgs=com.ibm.crypto.provider \
--truststore-password password \
--truststore-type JCERACFKS
```
+
+> **Note:** On IBM Java 17/21, the safkeyring URL protocol handler is provided via the
+> `java.net.spi.URLStreamHandlerProvider` SPI in module `ibm.crypto.zsecurity`.
+> The legacy `-Djava.protocol.handler.pkgs=com.ibm.crypto.provider` property
+> has no effect on Java 17+ and should not be used.
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
index 8feecfa6e3..0b2a05d35a 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/HttpClientWrapper.java
@@ -28,8 +28,6 @@
* Supports custom {@link SSLContext} and {@link HostnameVerifier} for
* STRICT, NONSTRICT, and DISABLED certificate verification modes.
*/
-// TODO: REMOVE all "DEBUG [HttpClientWrapper]" logging lines after SAF keyring issue is resolved
-@SuppressWarnings("squid:S106")
public class HttpClientWrapper {
private static final int CONNECT_TIMEOUT = 5000;
@@ -65,11 +63,6 @@ public HttpClientWrapper() {
}
public Response executeCall(URL url, Map headers) throws IOException {
- System.out.println("DEBUG [HttpClientWrapper] executeCall() URL=" + url);
- System.out.println("DEBUG [HttpClientWrapper] useHttps=" + useHttps);
- System.out.println("DEBUG [HttpClientWrapper] sslContext=" + (sslContext != null ? sslContext.getProtocol() : ""));
- System.out.println("DEBUG [HttpClientWrapper] hostnameVerifier=" + (hostnameVerifier != null ? hostnameVerifier.getClass().getName() : ""));
-
HttpURLConnection con;
if (useHttps) {
HttpsURLConnection httpsCon = (HttpsURLConnection) url.openConnection();
@@ -78,10 +71,8 @@ public Response executeCall(URL url, Map headers) throws IOExcep
httpsCon.setHostnameVerifier(hostnameVerifier);
}
con = httpsCon;
- System.out.println("DEBUG [HttpClientWrapper] HTTPS connection opened");
} else {
con = (HttpURLConnection) url.openConnection();
- System.out.println("DEBUG [HttpClientWrapper] HTTP connection opened");
}
con.setRequestMethod("GET");
@@ -94,16 +85,10 @@ public Response executeCall(URL url, Map headers) throws IOExcep
}
}
- System.out.println("DEBUG [HttpClientWrapper] Sending GET request (connectTimeout=" + CONNECT_TIMEOUT + "ms, readTimeout=" + READ_TIMEOUT + "ms)...");
try {
int responseCode = con.getResponseCode();
- System.out.println("DEBUG [HttpClientWrapper] Response code=" + responseCode);
String body = readBody(con);
- System.out.println("DEBUG [HttpClientWrapper] Response body read, length=" + (body != null ? body.length() : ""));
return new Response(responseCode, body);
- } catch (IOException e) {
- System.err.println("DEBUG [HttpClientWrapper] IOException during request: " + e.getClass().getName() + ": " + e.getMessage());
- throw e;
} finally {
con.disconnect();
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
index 1eedc7e8d3..40a1afd56d 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
@@ -24,7 +24,6 @@
* Checks z/OSMF JWK endpoint availability at {@code /jwt/ibm/api/zOSMFBuilder/jwk}.
* Interprets the HTTP response code to determine if the endpoint is functional
*/
-// TODO: REMOVE all "DEBUG [JwkEndpointChecker]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class JwkEndpointChecker {
@@ -46,19 +45,12 @@ public boolean check() {
Map headers = new HashMap<>();
headers.put(ZOSMF_CSRF_HEADER, "");
- System.out.println("DEBUG [JwkEndpointChecker] check() called");
- System.out.println("DEBUG [JwkEndpointChecker] target URL=" + urlString);
- System.out.println("DEBUG [JwkEndpointChecker] headers=" + headers);
-
try {
URL url = new URL(urlString);
System.out.println("Checking z/OSMF JWK endpoint: " + urlString);
- System.out.println("DEBUG [JwkEndpointChecker] Calling httpClient.executeCall()...");
HttpClientWrapper.Response response = httpClient.executeCall(url, headers);
- System.out.println("DEBUG [JwkEndpointChecker] Response received: HTTP " + response.getStatusCode());
- System.out.println("DEBUG [JwkEndpointChecker] Response body length=" + (response.getBody() != null ? response.getBody().length() : ""));
if (conf.isVerbose() && response.getBody() != null) {
System.out.println("Response body:\n" + response.getBody());
}
@@ -67,34 +59,24 @@ public boolean check() {
System.err.println("FAILURE: SSL handshake failed when connecting to " + urlString + ".");
System.err.println("Verify that the truststore contains the z/OSMF server certificate.");
System.err.println("Details: " + e.getMessage());
- System.err.println("DEBUG [JwkEndpointChecker] SSLHandshakeException stack trace:");
- e.printStackTrace(System.err);
return false;
} catch (ConnectException e) {
System.err.println("FAILURE: Cannot connect to " + conf.getZosmfHost() + ":" + conf.getZosmfPort() + ".");
System.err.println("Verify the host and port are correct and z/OSMF is running.");
System.err.println("Details: " + e.getMessage());
- System.err.println("DEBUG [JwkEndpointChecker] ConnectException stack trace:");
- e.printStackTrace(System.err);
return false;
} catch (SocketTimeoutException e) {
System.err.println("FAILURE: Connection timed out to " + conf.getZosmfHost() + ":" + conf.getZosmfPort() + ".");
System.err.println("This is commonly caused by an incorrect host/port or a firewall blocking the connection.");
System.err.println("Verify the z/OSMF host and port are correct and that no firewall is blocking access.");
- System.err.println("DEBUG [JwkEndpointChecker] SocketTimeoutException stack trace:");
- e.printStackTrace(System.err);
return false;
} catch (UnknownHostException e) {
System.err.println("FAILURE: Error when calling " + urlString + " verify hostname and port.");
System.err.println("The host '" + conf.getZosmfHost() + "' could not be resolved.");
- System.err.println("DEBUG [JwkEndpointChecker] UnknownHostException stack trace:");
- e.printStackTrace(System.err);
return false;
} catch (Exception e) {
System.err.println("FAILURE: Error when calling " + urlString + " verify hostname and port.");
System.err.println("Details: " + e.getMessage());
- System.err.println("DEBUG [JwkEndpointChecker] Exception class=" + e.getClass().getName());
- e.printStackTrace(System.err);
return false;
}
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
index c3e1b034a0..8e4f8cedaf 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
@@ -27,7 +27,6 @@
* {@link #initTrustAllSSLContext()} — trust-all mode for DISABLED verification
*
*/
-// TODO: REMOVE all "DEBUG [SSLContextFactory]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class SSLContextFactory {
@@ -49,19 +48,15 @@ public SSLContext getSslContext() {
* @return factory holding the initialized SSLContext
*/
public static SSLContextFactory initSSLContext(Stores stores) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException, CertificateException, IOException {
- System.out.println("DEBUG [SSLContextFactory] initSSLContext() called");
SSLContextFactory factory = new SSLContextFactory(stores);
- System.out.println("DEBUG [SSLContextFactory] Initializing TrustManagerFactory with trustStore (type=" + stores.getTrustStore().getType() + ", size=" + stores.getTrustStore().size() + ")");
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(stores.getTrustStore());
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
if (stores.getKeyStore() != null) {
- System.out.println("DEBUG [SSLContextFactory] Initializing KeyManagerFactory with keyStore (type=" + stores.getKeyStore().getType() + ", size=" + stores.getKeyStore().size() + ")");
keyFactory.init(stores.getKeyStore(), stores.getConf().getKeyStorePassword().toCharArray());
} else {
- System.out.println("DEBUG [SSLContextFactory] No keyStore, using empty keystore for KeyManagerFactory");
KeyStore emptyKeystore = KeyStore.getInstance(KeyStore.getDefaultType());
emptyKeystore.load(null, null);
keyFactory.init(emptyKeystore, null);
@@ -69,7 +64,6 @@ public static SSLContextFactory initSSLContext(Stores stores) throws NoSuchAlgor
factory.sslContext = SSLContext.getInstance("TLSv1.2");
factory.sslContext.init(keyFactory.getKeyManagers(), trustFactory.getTrustManagers(), new SecureRandom());
- System.out.println("DEBUG [SSLContextFactory] SSLContext created successfully (protocol=" + factory.sslContext.getProtocol() + ")");
return factory;
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
index a47ec2ef2e..90a3a401f5 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
@@ -27,7 +27,6 @@
* Loads Java {@link java.security.KeyStore} instances from the filesystem
* or z/OS SAF keyrings. Supports PKCS12, JKS, and {@code safkeyring://} URIs.
*/
-// TODO: REMOVE all "DEBUG [Stores]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class Stores {
@@ -70,38 +69,21 @@ public static String formatKeyringUrl(String input) {
}
void init() {
- System.out.println("DEBUG [Stores] init() called");
- System.out.println("DEBUG [Stores] trustStore path=" + conf.getTrustStore());
- System.out.println("DEBUG [Stores] trustStore type=" + conf.getTrustStoreType());
- System.out.println("DEBUG [Stores] trustStore isKeyring=" + isKeyring(conf.getTrustStore()));
- System.out.println("DEBUG [Stores] keyStore path=" + conf.getKeyStore());
- System.out.println("DEBUG [Stores] keyStore type=" + (conf.getKeyStore() != null ? conf.getKeyStoreType() : ""));
- System.out.println("DEBUG [Stores] keyStore isKeyring=" + isKeyring(conf.getKeyStore()));
- System.out.println("DEBUG [Stores] java.protocol.handler.pkgs=" + System.getProperty("java.protocol.handler.pkgs", ""));
try {
- System.out.println("DEBUG [Stores] Calling initKeystore()...");
initKeystore();
- System.out.println("DEBUG [Stores] initKeystore() completed. trustStore set by keystore=" + (trustStore != null));
if (trustStore == null) {
- System.out.println("DEBUG [Stores] Calling initTruststore()...");
initTruststore();
- System.out.println("DEBUG [Stores] initTruststore() completed successfully.");
}
} catch (FileNotFoundException e) {
- System.err.println("DEBUG [Stores] FileNotFoundException in init(): " + e.getMessage());
- e.printStackTrace(System.err);
throw new StoresNotInitializeException("Error while loading keystore file. Error message: " + e.getMessage() + "\n" +
"Possible solution: Verify correct path to the keystore. Change owner or permission to the keystore file.");
} catch (Exception e) {
- System.err.println("DEBUG [Stores] Exception in init(): " + e.getClass().getName() + ": " + e.getMessage());
- e.printStackTrace(System.err);
throw new StoresNotInitializeException(e.getMessage());
}
}
private void initTruststore() throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
if (conf.getTrustStore() == null) {
- System.out.println("DEBUG [Stores] initTruststore: No truststore specified, will use empty.");
try {
this.trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
this.trustStore.load(null, null);
@@ -111,48 +93,31 @@ private void initTruststore() throws IOException, CertificateException, NoSuchAl
return;
}
if (isKeyring(conf.getTrustStore())) {
- System.out.println("DEBUG [Stores] initTruststore: Detected SAF keyring URI: " + conf.getTrustStore());
- String formatted = formatKeyringUrl(conf.getTrustStore());
- System.out.println("DEBUG [Stores] initTruststore: Formatted keyring URL: " + formatted);
- System.out.println("DEBUG [Stores] initTruststore: Calling keyRingUrl()...");
URL url = keyRingUrl(conf.getTrustStore());
- System.out.println("DEBUG [Stores] initTruststore: URL object created: " + url + " (protocol=" + url.getProtocol() + ")");
- System.out.println("DEBUG [Stores] initTruststore: Calling url.openStream()...");
try (InputStream trustStoreIStream = url.openStream()) {
- System.out.println("DEBUG [Stores] initTruststore: openStream() succeeded. Loading keystore type=" + conf.getTrustStoreType());
this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
- System.out.println("DEBUG [Stores] initTruststore: Truststore loaded from keyring. Aliases count=" + trustStore.size());
}
} else {
- System.out.println("DEBUG [Stores] initTruststore: Loading from file: " + conf.getTrustStore());
try (InputStream trustStoreIStream = new FileInputStream(conf.getTrustStore())) {
this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
- System.out.println("DEBUG [Stores] initTruststore: Truststore loaded from file. Aliases count=" + trustStore.size());
}
}
}
private void initKeystore() throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
if (conf.getKeyStore() == null) {
- System.out.println("DEBUG [Stores] initKeystore: No keystore specified, skipping.");
return;
}
if (isKeyring(conf.getKeyStore())) {
- System.out.println("DEBUG [Stores] initKeystore: Detected SAF keyring URI: " + conf.getKeyStore());
try (InputStream keyringIStream = keyRingUrl(conf.getKeyStore()).openStream()) {
this.keyStore = readKeyStore(keyringIStream, conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
this.trustStore = this.keyStore;
- System.out.println("DEBUG [Stores] initKeystore: Keystore loaded from keyring. Aliases count=" + keyStore.size());
} catch (Exception e) {
- System.err.println("DEBUG [Stores] initKeystore: Exception loading keyring: " + e.getClass().getName() + ": " + e.getMessage());
- e.printStackTrace(System.err);
throw new StoresNotInitializeException(e.getMessage());
}
} else {
- System.out.println("DEBUG [Stores] initKeystore: Loading from file: " + conf.getKeyStore());
try (InputStream keyStoreIStream = new FileInputStream(conf.getKeyStore())) {
this.keyStore = readKeyStore(keyStoreIStream, conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
- System.out.println("DEBUG [Stores] initKeystore: Keystore loaded from file. Aliases count=" + keyStore.size());
}
}
}
@@ -181,21 +146,15 @@ public static URL keyRingUrl(String uri) throws MalformedURLException {
+ ". Make sure you use format safkeyring://userId/keyRing");
}
String formatted = formatKeyringUrl(uri);
- System.out.println("DEBUG [Stores] keyRingUrl: Creating URL from: " + formatted);
try {
- URL url = new URL(formatted);
- System.out.println("DEBUG [Stores] keyRingUrl: URL created successfully. protocol=" + url.getProtocol()
- + " host=" + url.getHost() + " path=" + url.getPath());
- return url;
+ return new URL(formatted);
} catch (MalformedURLException e) {
- System.out.println("DEBUG [Stores] keyRingUrl: Standard URL creation failed, trying explicit handler lookup...");
URL fallbackUrl = createUrlWithExplicitHandler(formatted);
if (fallbackUrl != null) {
- System.out.println("DEBUG [Stores] keyRingUrl: URL created via explicit handler. protocol=" + fallbackUrl.getProtocol());
return fallbackUrl;
}
- System.err.println("DEBUG [Stores] keyRingUrl: MalformedURLException for '" + formatted + "': " + e.getMessage());
- System.err.println("DEBUG [Stores] keyRingUrl: java.protocol.handler.pkgs=" + System.getProperty("java.protocol.handler.pkgs", ""));
+ System.err.println("ERROR: Unknown protocol in '" + formatted + "': " + e.getMessage());
+ System.err.println("Ensure the JVM is started with: --add-modules ibm.crypto.zsecurity,ibm.crypto.hdwrcca");
throw e;
}
}
@@ -206,17 +165,13 @@ public static URL keyRingUrl(String uri) throws MalformedURLException {
* {@code ibm.crypto.zsecurity} and {@code ibm.crypto.hdwrcca}.
*
* If the modules are resolved (via {@code --add-modules}), the standard
- * {@code new URL()} works. This fallback handles the case where the module IS
- * resolved but the ServiceLoader lookup failed, or where we can reflectively
- * instantiate the provider and obtain a handler.
+ * {@code new URL()} constructor works via ServiceLoader. This fallback handles
+ * the unlikely case where the module IS resolved but the automatic ServiceLoader
+ * discovery failed, by reflectively instantiating the SPI provider directly.
*/
private static URL createUrlWithExplicitHandler(String formatted) {
- // Extract protocol from the formatted URI (e.g., "safkeyring", "safkeyringjce", etc.)
String protocol = formatted.substring(0, formatted.indexOf(':'));
- System.out.println("DEBUG [Stores] createUrlWithExplicitHandler: protocol='" + protocol + "' formatted='" + formatted + "'");
- // Try 1: Use the URLStreamHandlerProvider SPI classes directly (Java 17/21 on z/OS)
- // These are the actual provider classes found in ibm.crypto.zsecurity and ibm.crypto.hdwrcca
String[] spiProviderClasses = {
"com.ibm.crypto.zsecurity.provider.safkeyring.Provider",
"com.ibm.crypto.hdwrCCA.provider.safkeyring.Provider"
@@ -228,68 +183,23 @@ private static URL createUrlWithExplicitHandler(String formatted) {
Thread.currentThread().getContextClassLoader(),
Stores.class.getClassLoader()
};
- String[] loaderNames = {"SystemClassLoader", "PlatformClassLoader", "ContextClassLoader", "StoresClassLoader"};
- System.out.println("DEBUG [Stores] createUrlWithExplicitHandler: Trying SPI providers...");
for (String providerClassName : spiProviderClasses) {
- for (int i = 0; i < loaders.length; i++) {
- ClassLoader loader = loaders[i];
- if (loader == null) {
- System.out.println("DEBUG [Stores] Skipping null loader: " + loaderNames[i]);
- continue;
- }
+ for (ClassLoader loader : loaders) {
+ if (loader == null) continue;
try {
- System.out.println("DEBUG [Stores] Trying SPI class=" + providerClassName + " loader=" + loaderNames[i] + " (" + loader.getClass().getName() + ")");
Class> cls = Class.forName(providerClassName, true, loader);
- System.out.println("DEBUG [Stores] Class loaded successfully: " + cls.getName());
java.net.spi.URLStreamHandlerProvider provider =
(java.net.spi.URLStreamHandlerProvider) cls.getDeclaredConstructor().newInstance();
- System.out.println("DEBUG [Stores] Provider instantiated: " + provider.getClass().getName());
java.net.URLStreamHandler handler = provider.createURLStreamHandler(protocol);
if (handler != null) {
- System.out.println("DEBUG [Stores] SUCCESS: Handler obtained for protocol '" + protocol + "' from " + providerClassName + " via " + loaderNames[i]);
return new URL(null, formatted, handler);
- } else {
- System.out.println("DEBUG [Stores] Provider returned null handler for protocol '" + protocol + "' (not supported by this provider)");
}
- } catch (ClassNotFoundException e) {
- System.out.println("DEBUG [Stores] ClassNotFoundException: " + providerClassName + " not found via " + loaderNames[i]);
- } catch (Exception e) {
- System.out.println("DEBUG [Stores] Exception: " + e.getClass().getName() + ": " + e.getMessage());
+ } catch (Exception ignored) {
+ // Try next combination
}
}
}
-
- // Try 2: Legacy approach - look for Handler class directly (Java 8 style)
- System.out.println("DEBUG [Stores] createUrlWithExplicitHandler: SPI providers not found. Trying legacy Handler classes...");
- String[] handlerPackages = {
- "com.ibm.crypto.provider",
- "com.ibm.crypto.zsecurity.provider",
- "com.ibm.crypto.hdwrCCA.provider"
- };
-
- for (String pkg : handlerPackages) {
- String className = pkg + "." + protocol + ".Handler";
- for (int i = 0; i < loaders.length; i++) {
- ClassLoader loader = loaders[i];
- if (loader == null) continue;
- try {
- System.out.println("DEBUG [Stores] Trying Handler class=" + className + " loader=" + loaderNames[i]);
- Class> cls = Class.forName(className, true, loader);
- java.net.URLStreamHandler handler = (java.net.URLStreamHandler) cls.getDeclaredConstructor().newInstance();
- System.out.println("DEBUG [Stores] SUCCESS: Handler instantiated: " + className + " via " + loaderNames[i]);
- return new URL(null, formatted, handler);
- } catch (ClassNotFoundException e) {
- System.out.println("DEBUG [Stores] ClassNotFoundException: " + className + " not found via " + loaderNames[i]);
- } catch (Exception e) {
- System.out.println("DEBUG [Stores] Exception: " + e.getClass().getName() + ": " + e.getMessage());
- }
- }
- }
-
- System.err.println("DEBUG [Stores] createUrlWithExplicitHandler: FAILED - Could not find handler for protocol '" + protocol + "' in any known location.");
- System.err.println("DEBUG [Stores] createUrlWithExplicitHandler: Ensure the JVM is started with: --add-modules ibm.crypto.zsecurity,ibm.crypto.hdwrcca");
- System.err.println("DEBUG [Stores] createUrlWithExplicitHandler: Resolved modules can be checked with: java --show-module-resolution --add-modules ibm.crypto.zsecurity -version");
return null;
}
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
index 84f6380145..24c04d59fe 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
@@ -21,7 +21,6 @@
*
* Exit codes: 0 = success, 4 = failure/error, 8 = help displayed.
*/
-// TODO: REMOVE all "DEBUG [ZosmfJwtCheck]" logging lines after SAF keyring issue is resolved
@SuppressWarnings("squid:S106")
public class ZosmfJwtCheck {
@@ -30,18 +29,6 @@ public class ZosmfJwtCheck {
static final String VERIFY_DISABLED = "DISABLED";
public static int mainWithExitCode(String[] args) {
- System.out.println("DEBUG [ZosmfJwtCheck] ===== zosmf-jwt-check starting =====");
- System.out.println("DEBUG [ZosmfJwtCheck] Java version: " + System.getProperty("java.version"));
- System.out.println("DEBUG [ZosmfJwtCheck] Java vendor: " + System.getProperty("java.vendor"));
- System.out.println("DEBUG [ZosmfJwtCheck] OS name: " + System.getProperty("os.name"));
- System.out.println("DEBUG [ZosmfJwtCheck] java.protocol.handler.pkgs (BEFORE ensureSafkeyringHandler): "
- + System.getProperty("java.protocol.handler.pkgs", ""));
-
- ensureSafkeyringHandler();
-
- System.out.println("DEBUG [ZosmfJwtCheck] java.protocol.handler.pkgs (AFTER ensureSafkeyringHandler): "
- + System.getProperty("java.protocol.handler.pkgs", ""));
-
try {
ZosmfJwtCheckConf conf = new ZosmfJwtCheckConf();
CommandLine cmd = new CommandLine(conf);
@@ -53,17 +40,6 @@ public static int mainWithExitCode(String[] args) {
return 8;
}
- System.out.println("DEBUG [ZosmfJwtCheck] Parsed config:");
- System.out.println("DEBUG [ZosmfJwtCheck] zosmf-host=" + conf.getZosmfHost());
- System.out.println("DEBUG [ZosmfJwtCheck] zosmf-port=" + conf.getZosmfPort());
- System.out.println("DEBUG [ZosmfJwtCheck] scheme=" + conf.getScheme());
- System.out.println("DEBUG [ZosmfJwtCheck] verify-certificates=" + conf.getVerifyCertificates());
- System.out.println("DEBUG [ZosmfJwtCheck] truststore-file=" + conf.getTrustStore());
- System.out.println("DEBUG [ZosmfJwtCheck] truststore-type=" + conf.getTrustStoreType());
- System.out.println("DEBUG [ZosmfJwtCheck] truststore-isKeyring=" + Stores.isKeyring(conf.getTrustStore()));
- System.out.println("DEBUG [ZosmfJwtCheck] keystore-file=" + conf.getKeyStore());
- System.out.println("DEBUG [ZosmfJwtCheck] keystore-type=" + conf.getKeyStoreType());
-
validateConfig(conf);
HttpClientWrapper httpClient;
@@ -75,11 +51,8 @@ public static int mainWithExitCode(String[] args) {
HostnameVerifier noopVerifier = (hostname, session) -> true;
httpClient = new HttpClientWrapper(sslContextFactory.getSslContext(), noopVerifier);
} else {
- System.out.println("DEBUG [ZosmfJwtCheck] Creating Stores (truststore/keystore)...");
Stores stores = new Stores(conf);
- System.out.println("DEBUG [ZosmfJwtCheck] Stores created successfully. Building SSLContext...");
SSLContextFactory sslContextFactory = SSLContextFactory.initSSLContext(stores);
- System.out.println("DEBUG [ZosmfJwtCheck] SSLContext created successfully.");
HostnameVerifier hostnameVerifier;
if (VERIFY_NONSTRICT.equals(verifyMode)) {
@@ -95,13 +68,9 @@ public static int mainWithExitCode(String[] args) {
}
JwkEndpointChecker checker = new JwkEndpointChecker(httpClient, conf);
- System.out.println("DEBUG [ZosmfJwtCheck] Calling JwkEndpointChecker.check()...");
- boolean success = checker.check();
- System.out.println("DEBUG [ZosmfJwtCheck] JwkEndpointChecker.check() returned: " + success);
- return success ? 0 : 4;
+ return checker.check() ? 0 : 4;
} catch (Exception e) {
System.err.println("ERROR: " + e.getMessage());
- System.err.println("DEBUG [ZosmfJwtCheck] Exception class: " + e.getClass().getName());
e.printStackTrace(System.err);
return 4;
}
@@ -129,42 +98,6 @@ static void validateConfig(ZosmfJwtCheckConf conf) {
}
}
- /**
- * Attempts to register the IBM SAF keyring URL protocol handler via the legacy
- * {@code java.protocol.handler.pkgs} system property.
- *
- * Note: On IBM Java 17/21 (z/OS), the safkeyring handler is registered
- * via the {@code java.net.spi.URLStreamHandlerProvider} SPI in module
- * {@code ibm.crypto.zsecurity}, so this property has no effect. The real fix is
- * to launch the JVM with {@code --add-modules ibm.crypto.zsecurity}.
- * This method is retained only as a best-effort fallback for IBM Java 8.
- */
- static void ensureSafkeyringHandler() {
- String[] packagePrefixes = {
- "com.ibm.crypto.zsecurity.provider",
- "com.ibm.crypto.hdwrCCA.provider"
- };
- String existing = System.getProperty("java.protocol.handler.pkgs", "");
- System.out.println("DEBUG [ZosmfJwtCheck] ensureSafkeyringHandler: existing java.protocol.handler.pkgs='" + existing + "'");
- StringBuilder sb = new StringBuilder(existing);
- for (String prefix : packagePrefixes) {
- if (!existing.contains(prefix)) {
- if (sb.length() > 0) {
- sb.append('|');
- }
- sb.append(prefix);
- System.out.println("DEBUG [ZosmfJwtCheck] ensureSafkeyringHandler: Adding package prefix: " + prefix);
- } else {
- System.out.println("DEBUG [ZosmfJwtCheck] ensureSafkeyringHandler: Already present: " + prefix);
- }
- }
- String finalValue = sb.toString();
- System.setProperty("java.protocol.handler.pkgs", finalValue);
- System.out.println("DEBUG [ZosmfJwtCheck] ensureSafkeyringHandler: Final java.protocol.handler.pkgs='" + finalValue + "'");
- System.out.println("DEBUG [ZosmfJwtCheck] ensureSafkeyringHandler: NOTE - On Java 17/21, this property has no effect.");
- System.out.println("DEBUG [ZosmfJwtCheck] ensureSafkeyringHandler: The safkeyring handler is loaded via SPI from module ibm.crypto.zsecurity (requires --add-modules).");
- }
-
public static void main(String[] args) {
System.exit(mainWithExitCode(args));
}
From 246af1a9f972e8b50821653a99c4b87036e38d8a Mon Sep 17 00:00:00 2001
From: "Signed-off-by: hrishikesh-nalawade"
Date: Mon, 25 May 2026 23:51:42 +0530
Subject: [PATCH 21/28] fallback with diagnostics
Signed-off-by: hrishikesh-nalawade
---
.../src/main/java/org/zowe/apiml/Stores.java | 60 +++++++++++++++----
.../java/org/zowe/apiml/ZosmfJwtCheck.java | 43 +++++++++++++
2 files changed, 93 insertions(+), 10 deletions(-)
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
index 90a3a401f5..64d3de869f 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
@@ -147,10 +147,15 @@ public static URL keyRingUrl(String uri) throws MalformedURLException {
}
String formatted = formatKeyringUrl(uri);
try {
- return new URL(formatted);
+ URL url = new URL(formatted);
+ System.out.println("DIAG: new URL('" + formatted + "') succeeded via java.protocol.handler.pkgs");
+ return url;
} catch (MalformedURLException e) {
+ System.out.println("DIAG: new URL('" + formatted + "') failed: " + e.getMessage());
+ System.out.println("DIAG: Attempting explicit handler fallback...");
URL fallbackUrl = createUrlWithExplicitHandler(formatted);
if (fallbackUrl != null) {
+ System.out.println("DIAG: Fallback succeeded for '" + formatted + "'");
return fallbackUrl;
}
System.err.println("ERROR: Unknown protocol in '" + formatted + "': " + e.getMessage());
@@ -160,23 +165,23 @@ public static URL keyRingUrl(String uri) throws MalformedURLException {
}
/**
- * Fallback for IBM Java 17/21+ on z/OS where the safkeyring handler is registered
- * via the {@code java.net.spi.URLStreamHandlerProvider} SPI mechanism in modules
+ * Fallback for IBM Java 17/21+ on z/OS where the safkeyring handler may be
+ * registered via the {@code java.net.spi.URLStreamHandlerProvider} SPI or
+ * via a legacy {@code ..Handler} class in modules
* {@code ibm.crypto.zsecurity} and {@code ibm.crypto.hdwrcca}.
*
- * If the modules are resolved (via {@code --add-modules}), the standard
- * {@code new URL()} constructor works via ServiceLoader. This fallback handles
- * the unlikely case where the module IS resolved but the automatic ServiceLoader
- * discovery failed, by reflectively instantiating the SPI provider directly.
+ * Requires the modules to be resolved via {@code --add-modules}.
*/
private static URL createUrlWithExplicitHandler(String formatted) {
String protocol = formatted.substring(0, formatted.indexOf(':'));
+ // Try 1: SPI URLStreamHandlerProvider classes
String[] spiProviderClasses = {
"com.ibm.crypto.zsecurity.provider.safkeyring.Provider",
"com.ibm.crypto.hdwrCCA.provider.safkeyring.Provider"
};
+ String[] loaderNames = {"SystemCL", "PlatformCL", "ContextCL", "StoresCL"};
ClassLoader[] loaders = {
ClassLoader.getSystemClassLoader(),
ClassLoader.getPlatformClassLoader(),
@@ -184,8 +189,11 @@ private static URL createUrlWithExplicitHandler(String formatted) {
Stores.class.getClassLoader()
};
+ System.out.println("DIAG: createUrlWithExplicitHandler: protocol='" + protocol + "'");
+ System.out.println("DIAG: Trying SPI providers...");
for (String providerClassName : spiProviderClasses) {
- for (ClassLoader loader : loaders) {
+ for (int i = 0; i < loaders.length; i++) {
+ ClassLoader loader = loaders[i];
if (loader == null) continue;
try {
Class> cls = Class.forName(providerClassName, true, loader);
@@ -193,13 +201,45 @@ private static URL createUrlWithExplicitHandler(String formatted) {
(java.net.spi.URLStreamHandlerProvider) cls.getDeclaredConstructor().newInstance();
java.net.URLStreamHandler handler = provider.createURLStreamHandler(protocol);
if (handler != null) {
+ System.out.println("DIAG: SUCCESS via SPI: " + providerClassName + " [" + loaderNames[i] + "]");
return new URL(null, formatted, handler);
+ } else {
+ System.out.println("DIAG: SPI " + providerClassName + " [" + loaderNames[i] + "] returned null handler");
}
- } catch (Exception ignored) {
- // Try next combination
+ } catch (ClassNotFoundException e) {
+ System.out.println("DIAG: SPI " + providerClassName + " [" + loaderNames[i] + "] ClassNotFound");
+ } catch (Exception e) {
+ System.out.println("DIAG: SPI " + providerClassName + " [" + loaderNames[i] + "] " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
}
}
+
+ // Try 2: Legacy Handler classes (e.g. .safkeyring.Handler)
+ String[] handlerPackages = {
+ "com.ibm.crypto.zsecurity.provider",
+ "com.ibm.crypto.hdwrCCA.provider"
+ };
+
+ System.out.println("DIAG: Trying legacy Handler classes...");
+ for (String pkg : handlerPackages) {
+ String className = pkg + "." + protocol + ".Handler";
+ for (int i = 0; i < loaders.length; i++) {
+ ClassLoader loader = loaders[i];
+ if (loader == null) continue;
+ try {
+ Class> cls = Class.forName(className, true, loader);
+ java.net.URLStreamHandler handler = (java.net.URLStreamHandler) cls.getDeclaredConstructor().newInstance();
+ System.out.println("DIAG: SUCCESS via Handler: " + className + " [" + loaderNames[i] + "]");
+ return new URL(null, formatted, handler);
+ } catch (ClassNotFoundException e) {
+ System.out.println("DIAG: Handler " + className + " [" + loaderNames[i] + "] ClassNotFound");
+ } catch (Exception e) {
+ System.out.println("DIAG: Handler " + className + " [" + loaderNames[i] + "] " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ }
+ }
+ }
+
+ System.out.println("DIAG: createUrlWithExplicitHandler: ALL attempts failed");
return null;
}
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
index 24c04d59fe..c13a61e1e7 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
@@ -29,6 +29,21 @@ public class ZosmfJwtCheck {
static final String VERIFY_DISABLED = "DISABLED";
public static int mainWithExitCode(String[] args) {
+ System.out.println("DIAG: Java=" + System.getProperty("java.version") + " Vendor=" + System.getProperty("java.vendor"));
+ System.out.println("DIAG: java.protocol.handler.pkgs (before)=" + System.getProperty("java.protocol.handler.pkgs", ""));
+
+ ensureSafkeyringHandler();
+
+ System.out.println("DIAG: java.protocol.handler.pkgs (after)=" + System.getProperty("java.protocol.handler.pkgs", ""));
+
+ // Check if ibm.crypto.zsecurity module is resolved (confirms --add-modules worked)
+ try {
+ Class> testClass = Class.forName("com.ibm.crypto.zsecurity.provider.safkeyring.Provider");
+ System.out.println("DIAG: Module ibm.crypto.zsecurity IS resolved (class found: " + testClass.getName() + ")");
+ } catch (ClassNotFoundException e) {
+ System.out.println("DIAG: Module ibm.crypto.zsecurity NOT resolved (ClassNotFoundException). --add-modules may be missing.");
+ }
+
try {
ZosmfJwtCheckConf conf = new ZosmfJwtCheckConf();
CommandLine cmd = new CommandLine(conf);
@@ -98,6 +113,34 @@ static void validateConfig(ZosmfJwtCheckConf conf) {
}
}
+ /**
+ * Registers IBM SAF keyring URL protocol handler packages via the
+ * {@code java.protocol.handler.pkgs} system property.
+ *
+ * On IBM Java 17/21 (z/OS), this property works in conjunction with
+ * {@code --add-modules ibm.crypto.zsecurity,ibm.crypto.hdwrcca} to enable
+ * the {@code safkeyring://} URL protocol. The {@code --add-modules} flag
+ * resolves the module (making classes accessible), while this property tells
+ * the {@link java.net.URL} class which packages to search for the handler.
+ */
+ static void ensureSafkeyringHandler() {
+ String[] packagePrefixes = {
+ "com.ibm.crypto.zsecurity.provider",
+ "com.ibm.crypto.hdwrCCA.provider"
+ };
+ String existing = System.getProperty("java.protocol.handler.pkgs", "");
+ StringBuilder sb = new StringBuilder(existing);
+ for (String prefix : packagePrefixes) {
+ if (!existing.contains(prefix)) {
+ if (sb.length() > 0) {
+ sb.append('|');
+ }
+ sb.append(prefix);
+ }
+ }
+ System.setProperty("java.protocol.handler.pkgs", sb.toString());
+ }
+
public static void main(String[] args) {
System.exit(mainWithExitCode(args));
}
From 5538f73a4d5c236ef83a8e65fa4c8f9e1c55b01d Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Tue, 26 May 2026 01:44:14 +0530
Subject: [PATCH 22/28] Fix after testing fallbacks
Signed-off-by: hrishikesh-nalawade
---
zosmf-jwt-check/README.md | 10 +-
.../src/main/java/org/zowe/apiml/Stores.java | 91 +------------------
.../java/org/zowe/apiml/ZosmfJwtCheck.java | 13 ---
3 files changed, 7 insertions(+), 107 deletions(-)
diff --git a/zosmf-jwt-check/README.md b/zosmf-jwt-check/README.md
index ae7a9cbcb0..b72ce55b2c 100644
--- a/zosmf-jwt-check/README.md
+++ b/zosmf-jwt-check/README.md
@@ -307,7 +307,7 @@ java -jar zosmf-jwt-check-.jar --zosmf-host nonexistent.host --zosmf-po
On z/OS, if you are using SAF keyrings instead of file-based keystores/truststores, provide the keyring path in the `safkeyring://` format and add the IBM crypto modules to the JVM module graph:
```bash
-java --add-modules ibm.crypto.zsecurity \
+java --add-modules ibm.crypto.zsecurity,ibm.crypto.hdwrcca \
-jar zosmf-jwt-check-.jar \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
@@ -316,7 +316,7 @@ java --add-modules ibm.crypto.zsecurity \
--truststore-type JCERACFKS
```
-> **Note:** On IBM Java 17/21, the safkeyring URL protocol handler is provided via the
-> `java.net.spi.URLStreamHandlerProvider` SPI in module `ibm.crypto.zsecurity`.
-> The legacy `-Djava.protocol.handler.pkgs=com.ibm.crypto.provider` property
-> has no effect on Java 17+ and should not be used.
+> **Note:** On IBM Java 17/21, the `--add-modules` flag resolves the IBM crypto modules,
+> making the safkeyring URL protocol handler classes accessible. The tool also sets
+> `java.protocol.handler.pkgs` internally to register the handler packages with
+> the `URL` class. Both mechanisms work together to enable `safkeyring://` URLs.
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
index 64d3de869f..6d78e27f49 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
@@ -147,99 +147,12 @@ public static URL keyRingUrl(String uri) throws MalformedURLException {
}
String formatted = formatKeyringUrl(uri);
try {
- URL url = new URL(formatted);
- System.out.println("DIAG: new URL('" + formatted + "') succeeded via java.protocol.handler.pkgs");
- return url;
+ return new URL(formatted);
} catch (MalformedURLException e) {
- System.out.println("DIAG: new URL('" + formatted + "') failed: " + e.getMessage());
- System.out.println("DIAG: Attempting explicit handler fallback...");
- URL fallbackUrl = createUrlWithExplicitHandler(formatted);
- if (fallbackUrl != null) {
- System.out.println("DIAG: Fallback succeeded for '" + formatted + "'");
- return fallbackUrl;
- }
System.err.println("ERROR: Unknown protocol in '" + formatted + "': " + e.getMessage());
System.err.println("Ensure the JVM is started with: --add-modules ibm.crypto.zsecurity,ibm.crypto.hdwrcca");
+ System.err.println("And that ensureSafkeyringHandler() has been called to set java.protocol.handler.pkgs");
throw e;
}
}
-
- /**
- * Fallback for IBM Java 17/21+ on z/OS where the safkeyring handler may be
- * registered via the {@code java.net.spi.URLStreamHandlerProvider} SPI or
- * via a legacy {@code ..Handler} class in modules
- * {@code ibm.crypto.zsecurity} and {@code ibm.crypto.hdwrcca}.
- *
- * Requires the modules to be resolved via {@code --add-modules}.
- */
- private static URL createUrlWithExplicitHandler(String formatted) {
- String protocol = formatted.substring(0, formatted.indexOf(':'));
-
- // Try 1: SPI URLStreamHandlerProvider classes
- String[] spiProviderClasses = {
- "com.ibm.crypto.zsecurity.provider.safkeyring.Provider",
- "com.ibm.crypto.hdwrCCA.provider.safkeyring.Provider"
- };
-
- String[] loaderNames = {"SystemCL", "PlatformCL", "ContextCL", "StoresCL"};
- ClassLoader[] loaders = {
- ClassLoader.getSystemClassLoader(),
- ClassLoader.getPlatformClassLoader(),
- Thread.currentThread().getContextClassLoader(),
- Stores.class.getClassLoader()
- };
-
- System.out.println("DIAG: createUrlWithExplicitHandler: protocol='" + protocol + "'");
- System.out.println("DIAG: Trying SPI providers...");
- for (String providerClassName : spiProviderClasses) {
- for (int i = 0; i < loaders.length; i++) {
- ClassLoader loader = loaders[i];
- if (loader == null) continue;
- try {
- Class> cls = Class.forName(providerClassName, true, loader);
- java.net.spi.URLStreamHandlerProvider provider =
- (java.net.spi.URLStreamHandlerProvider) cls.getDeclaredConstructor().newInstance();
- java.net.URLStreamHandler handler = provider.createURLStreamHandler(protocol);
- if (handler != null) {
- System.out.println("DIAG: SUCCESS via SPI: " + providerClassName + " [" + loaderNames[i] + "]");
- return new URL(null, formatted, handler);
- } else {
- System.out.println("DIAG: SPI " + providerClassName + " [" + loaderNames[i] + "] returned null handler");
- }
- } catch (ClassNotFoundException e) {
- System.out.println("DIAG: SPI " + providerClassName + " [" + loaderNames[i] + "] ClassNotFound");
- } catch (Exception e) {
- System.out.println("DIAG: SPI " + providerClassName + " [" + loaderNames[i] + "] " + e.getClass().getSimpleName() + ": " + e.getMessage());
- }
- }
- }
-
- // Try 2: Legacy Handler classes (e.g. .safkeyring.Handler)
- String[] handlerPackages = {
- "com.ibm.crypto.zsecurity.provider",
- "com.ibm.crypto.hdwrCCA.provider"
- };
-
- System.out.println("DIAG: Trying legacy Handler classes...");
- for (String pkg : handlerPackages) {
- String className = pkg + "." + protocol + ".Handler";
- for (int i = 0; i < loaders.length; i++) {
- ClassLoader loader = loaders[i];
- if (loader == null) continue;
- try {
- Class> cls = Class.forName(className, true, loader);
- java.net.URLStreamHandler handler = (java.net.URLStreamHandler) cls.getDeclaredConstructor().newInstance();
- System.out.println("DIAG: SUCCESS via Handler: " + className + " [" + loaderNames[i] + "]");
- return new URL(null, formatted, handler);
- } catch (ClassNotFoundException e) {
- System.out.println("DIAG: Handler " + className + " [" + loaderNames[i] + "] ClassNotFound");
- } catch (Exception e) {
- System.out.println("DIAG: Handler " + className + " [" + loaderNames[i] + "] " + e.getClass().getSimpleName() + ": " + e.getMessage());
- }
- }
- }
-
- System.out.println("DIAG: createUrlWithExplicitHandler: ALL attempts failed");
- return null;
- }
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
index c13a61e1e7..6fb4619f3a 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
@@ -29,21 +29,8 @@ public class ZosmfJwtCheck {
static final String VERIFY_DISABLED = "DISABLED";
public static int mainWithExitCode(String[] args) {
- System.out.println("DIAG: Java=" + System.getProperty("java.version") + " Vendor=" + System.getProperty("java.vendor"));
- System.out.println("DIAG: java.protocol.handler.pkgs (before)=" + System.getProperty("java.protocol.handler.pkgs", ""));
-
ensureSafkeyringHandler();
- System.out.println("DIAG: java.protocol.handler.pkgs (after)=" + System.getProperty("java.protocol.handler.pkgs", ""));
-
- // Check if ibm.crypto.zsecurity module is resolved (confirms --add-modules worked)
- try {
- Class> testClass = Class.forName("com.ibm.crypto.zsecurity.provider.safkeyring.Provider");
- System.out.println("DIAG: Module ibm.crypto.zsecurity IS resolved (class found: " + testClass.getName() + ")");
- } catch (ClassNotFoundException e) {
- System.out.println("DIAG: Module ibm.crypto.zsecurity NOT resolved (ClassNotFoundException). --add-modules may be missing.");
- }
-
try {
ZosmfJwtCheckConf conf = new ZosmfJwtCheckConf();
CommandLine cmd = new CommandLine(conf);
From f1a5b2d223328ed8bcbb9af7b857b2b67fda2bf4 Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Fri, 29 May 2026 01:18:55 +0530
Subject: [PATCH 23/28] Single Preflight check JAR code
Signed-off-by: hrishikesh-nalawade
---
certificate-analyser/README.md | 154 ++++++++++++++++++
certificate-analyser/build.gradle | 2 +
.../main/java/org/zowe/apiml/Analyser.java | 9 +
.../java/org/zowe/apiml/AnalyserTest.java | 15 +-
zosmf-jwt-check/README.md | 85 ++++------
zosmf-jwt-check/build.gradle | 11 +-
.../org/zowe/apiml/SSLContextFactory.java | 8 +-
.../java/org/zowe/apiml/ZosmfJwtCheck.java | 2 +-
.../apiml/{Stores.java => ZosmfStores.java} | 4 +-
9 files changed, 218 insertions(+), 72 deletions(-)
rename zosmf-jwt-check/src/main/java/org/zowe/apiml/{Stores.java => ZosmfStores.java} (98%)
diff --git a/certificate-analyser/README.md b/certificate-analyser/README.md
index bec9673a7e..691fefb0e3 100644
--- a/certificate-analyser/README.md
+++ b/certificate-analyser/README.md
@@ -1,5 +1,40 @@
## Certificate validation tool
+### Build
+
+From the root of the `api-layer` repository:
+
+```bash
+./gradlew :certificate-analyser:build
+```
+
+On Windows:
+
+```powershell
+.\gradlew :certificate-analyser:build
+```
+
+The fat JAR (with all dependencies bundled, including z/OSMF JWT Check) will be generated at:
+
+```
+certificate-analyser/build/libs/certificate-analyser-.jar
+```
+
+#### Build Architecture
+
+The `certificate-analyser` module produces a **single unified fat JAR** that bundles:
+
+1. All certificate-analyser classes (certificate validation, local/remote handshake, local verification)
+2. All `zosmf-jwt-check` classes (z/OSMF JWK endpoint connectivity check)
+3. All shared dependencies (picocli)
+
+This is achieved through the following Gradle configuration:
+
+- `certificate-analyser/build.gradle` declares `implementation project(':zosmf-jwt-check')` as a dependency
+- The `jar` task uses `from { configurations.runtimeClasspath.collect { ... } }` to bundle all runtime dependencies into the fat JAR
+- `zosmf-jwt-check/build.gradle` produces a thin JAR (no `Main-Class` manifest, no fat-jar bundling) that is consumed only as a build dependency
+- The `zosmf-jwt-check` module **does not produce a standalone runnable JAR** — its functionality is accessed exclusively through the certificate-analyser JAR via the `--zosmf-jwt-check` CLI flag
+
### Usage
java -jar certificate-analyser-.jar --help
@@ -61,3 +96,122 @@ java --add-modules ibm.crypto.zsecurity,ibm.crypto.hdwrcca \
### Possible issues
Keystore/truststore is owned by different user - permission error. Temporarily Change read permission to all.
+
+---
+
+## z/OSMF JWT Check (embedded)
+
+This JAR also includes the **z/OSMF JWT Check** tool, which verifies connectivity to the z/OSMF JWK endpoint (`/jwt/ibm/api/zOSMFBuilder/jwk`). It helps diagnose configuration issues such as incorrect hostnames, unreachable ports, missing certificates, or misconfigured z/OSMF.
+
+To use the z/OSMF JWT check functionality, pass `--zosmf-jwt-check` as the **first** argument. All subsequent arguments are passed to the z/OSMF JWT check tool.
+
+### z/OSMF JWT Check — Help
+
+```bash
+java -jar certificate-analyser-.jar --zosmf-jwt-check --help
+```
+
+### z/OSMF JWT Check — CLI Flags
+
+#### Required Flags
+
+| Flag | Description | Example |
+|------|-------------|---------|
+| `--zosmf-host` | Hostname or IP address of the z/OSMF server | `--zosmf-host myzosmf.example.com` |
+| `--zosmf-port` | Port number of the z/OSMF server | `--zosmf-port 11443` |
+
+#### Conditionally Required Flags
+
+These flags are required when `--scheme=https` (the default) and `--verify-certificates` is **not** `DISABLED`:
+
+| Flag | Description |
+|------|-------------|
+| `--truststore-file` | Path to the truststore file containing the z/OSMF CA certificate |
+| `--truststore-password` | Password for the truststore. If specified without a value, you will be prompted interactively |
+
+#### Optional Flags
+
+| Flag | Default | Description |
+|------|---------|-------------|
+| `--scheme` | `https` | Protocol to use: `http` or `https` |
+| `--verify-certificates` | `STRICT` | Certificate verification mode: `STRICT`, `NONSTRICT`, or `DISABLED` |
+| `--truststore-type` | `PKCS12` | Format of the truststore file (e.g., `PKCS12`, `JKS`, `JCERACFKS`) |
+| `--keystore-file` | *(none)* | Path to keystore file (only needed for mutual TLS / client certificate authentication) |
+| `--keystore-password` | *(none)* | Password for the keystore. If specified without a value, you will be prompted interactively |
+| `--keystore-type` | `PKCS12` | Format of the keystore file |
+| `-v`, `--verbose` | | Print the response body from the endpoint |
+| `-h`, `--help` | | Display usage help and exit |
+
+### Certificate Verification Modes
+
+- **STRICT** (default) — Full certificate chain validation + hostname verification. Truststore required.
+- **NONSTRICT** — Certificate chain validated, hostname verification skipped. Truststore required.
+- **DISABLED** — No certificate validation. No truststore required. **Do not use in production.**
+
+### Exit Codes
+
+| Code | Meaning |
+|------|---------|
+| `0` | **Success** — z/OSMF JWK endpoint is reachable and responding |
+| `4` | **Failure** — connection failed, SSL error, endpoint not found, or configuration error |
+| `8` | **Help** — help/version was displayed; no check was performed |
+
+### z/OSMF JWT Check — Examples
+
+**Quick test (DISABLED mode — no truststore needed):**
+
+```bash
+java -jar certificate-analyser-.jar --zosmf-jwt-check \
+ --zosmf-host myzosmf.example.com \
+ --zosmf-port 11443 \
+ --verify-certificates DISABLED
+```
+
+**STRICT mode (full certificate verification):**
+
+```bash
+java -jar certificate-analyser-.jar --zosmf-jwt-check \
+ --zosmf-host myzosmf.example.com \
+ --zosmf-port 11443 \
+ --truststore-file /path/to/truststore.p12 \
+ --truststore-password changeit
+```
+
+**NONSTRICT mode (skip hostname check):**
+
+```bash
+java -jar certificate-analyser-.jar --zosmf-jwt-check \
+ --zosmf-host 10.0.0.50 \
+ --zosmf-port 11443 \
+ --truststore-file /path/to/truststore.p12 \
+ --truststore-password password \
+ --verify-certificates NONSTRICT
+```
+
+**HTTP mode (no SSL):**
+
+```bash
+java -jar certificate-analyser-.jar --zosmf-jwt-check \
+ --zosmf-host myzosmf.example.com \
+ --zosmf-port 80 \
+ --scheme http
+```
+
+### SAF Keyrings (z/OSMF JWT Check)
+
+On z/OS, if you are using SAF keyrings, provide the keyring path in `safkeyring://` format and add the JVM protocol handler:
+
+```bash
+java --add-modules ibm.crypto.zsecurity,ibm.crypto.hdwrcca \
+ -jar certificate-analyser-.jar --zosmf-jwt-check \
+ --zosmf-host myzosmf.example.com \
+ --zosmf-port 11443 \
+ --truststore-file safkeyring://IZUSVR/ZoweKeyring \
+ --truststore-password password \
+ --truststore-type JCERACFKS
+```
+
+> **Note:** On IBM Java 17/21, the `--add-modules` flag resolves the IBM crypto modules,
+> making the safkeyring URL protocol handler classes accessible. The tool also sets
+> `java.protocol.handler.pkgs` internally to register the handler packages with
+> the `URL` class. Both mechanisms work together to enable `safkeyring://` URLs.
diff --git a/certificate-analyser/build.gradle b/certificate-analyser/build.gradle
index 7080e138c0..d68085d549 100644
--- a/certificate-analyser/build.gradle
+++ b/certificate-analyser/build.gradle
@@ -3,6 +3,7 @@ plugins {
}
dependencies {
+ implementation project(':zosmf-jwt-check')
implementation libs.picocli
annotationProcessor libs.picocli.codegen
@@ -16,6 +17,7 @@ compileJava {
}
jar {
+ dependsOn ':zosmf-jwt-check:jar'
manifest {
attributes(
'Main-Class': 'org.zowe.apiml.Analyser'
diff --git a/certificate-analyser/src/main/java/org/zowe/apiml/Analyser.java b/certificate-analyser/src/main/java/org/zowe/apiml/Analyser.java
index 60c5f96c8c..6a35869898 100644
--- a/certificate-analyser/src/main/java/org/zowe/apiml/Analyser.java
+++ b/certificate-analyser/src/main/java/org/zowe/apiml/Analyser.java
@@ -13,13 +13,22 @@
import picocli.CommandLine;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
@SuppressWarnings("squid:S106") //ignoring the System.out System.err warnings
public class Analyser {
+ private static final String ZOSMF_JWT_CHECK_FLAG = "--zosmf-jwt-check";
+
public static int mainWithExitCode(String[] args) {
ensureSafkeyringHandler();
+
+ if (args.length > 0 && ZOSMF_JWT_CHECK_FLAG.equals(args[0])) {
+ String[] remainingArgs = Arrays.copyOfRange(args, 1, args.length);
+ return ZosmfJwtCheck.mainWithExitCode(remainingArgs);
+ }
+
try {
ApimlConf conf = new ApimlConf();
CommandLine cmd = new CommandLine(conf);
diff --git a/certificate-analyser/src/test/java/org/zowe/apiml/AnalyserTest.java b/certificate-analyser/src/test/java/org/zowe/apiml/AnalyserTest.java
index 64d33b251c..96e36e0a89 100644
--- a/certificate-analyser/src/test/java/org/zowe/apiml/AnalyserTest.java
+++ b/certificate-analyser/src/test/java/org/zowe/apiml/AnalyserTest.java
@@ -59,6 +59,19 @@ void whenHelpRequested_thenHelpIsPrintedAndExitCodeIs8() {
assertTrue(outputStream.toString().contains("Display a help message"));
}
+ @Test
+ void whenZosmfJwtCheckFlagPassed_thenDelegatesToZosmfJwtCheck() {
+ String[] args = {"--zosmf-jwt-check", "--help"};
+ assertEquals(8, Analyser.mainWithExitCode(args));
+ assertTrue(outputStream.toString().contains("z/OSMF JWT Check"));
+ }
+
+ @Test
+ void whenZosmfJwtCheckFlagWithNoArgs_thenReturnsExitCode4() {
+ String[] args = {"--zosmf-jwt-check"};
+ assertEquals(4, Analyser.mainWithExitCode(args));
+ }
+
@Test
void whenNoRemoteUrlProvided_thenMessageIsPrinted() {
String[] args = {};
@@ -149,4 +162,4 @@ void whenCalledViaMainWithExitCode_thenPropertyIsSet() {
assertTrue(value.contains("com.ibm.crypto.hdwrCCA.provider"));
}
}
-}
\ No newline at end of file
+}
diff --git a/zosmf-jwt-check/README.md b/zosmf-jwt-check/README.md
index b72ce55b2c..54a6516488 100644
--- a/zosmf-jwt-check/README.md
+++ b/zosmf-jwt-check/README.md
@@ -2,11 +2,13 @@
A Java utility that verifies connectivity to the z/OSMF JWK endpoint. This tool helps diagnose configuration issues early such as incorrect hostnames, unreachable ports, missing certificates, or misconfigured z/OSMF by performing a lightweight HTTP(S) call to the z/OSMF JWK endpoint at `/jwt/ibm/api/zOSMFBuilder/jwk`.
+> **Build Note:** This module no longer produces a standalone runnable JAR. Its functionality is bundled into the **certificate-analyser** fat JAR and accessed via the `--zosmf-jwt-check` CLI flag. See [Running via certificate-analyser](#running-via-certificate-analyser) below.
+
## Table of Contents
- [Prerequisites](#prerequisites)
- [Building](#building)
-- [Usage](#usage)
+- [Running via certificate-analyser](#running-via-certificate-analyser)
- [CLI Flags Reference](#cli-flags-reference)
- [Certificate Verification Modes](#certificate-verification-modes)
- [Exit Codes](#exit-codes)
@@ -29,36 +31,46 @@ A Java utility that verifies connectivity to the z/OSMF JWK endpoint. This tool
## Building
-From the root of the `api-layer` repository:
+From the root of the `api-layer` repository, build the unified certificate-analyser JAR (which includes this module):
```bash
-./gradlew :zosmf-jwt-check:build
+./gradlew :certificate-analyser:build
```
On Windows:
```powershell
-.\gradlew :zosmf-jwt-check:build
+.\gradlew :certificate-analyser:build
```
-The fat JAR (with all dependencies bundled) will be generated at:
+The fat JAR will be generated at:
```
-zosmf-jwt-check/build/libs/zosmf-jwt-check-.jar
+certificate-analyser/build/libs/certificate-analyser-.jar
+```
+
+To compile and test this module independently (without producing a runnable JAR):
+
+```bash
+./gradlew :zosmf-jwt-check:test
```
-For example: `zosmf-jwt-check/build/libs/zosmf-jwt-check-3.5.12-SNAPSHOT.jar`
+### Build Architecture
+
+This module produces a **thin JAR** (no `Main-Class`, no bundled dependencies) that is consumed as a compile/runtime dependency by `certificate-analyser`. The `certificate-analyser` fat JAR bundles all classes from this module and exposes the z/OSMF JWT check functionality via the `--zosmf-jwt-check` CLI flag.
-## Usage
+## Running via certificate-analyser
```bash
-java -jar zosmf-jwt-check-.jar --zosmf-host --zosmf-port [options]
+java -jar certificate-analyser/build/libs/certificate-analyser-.jar --zosmf-jwt-check [options]
```
-**Minimal example (DISABLED mode, quickest way to test):**
+All arguments after `--zosmf-jwt-check` are passed directly to the z/OSMF JWT check tool.
+
+**Minimal example (DISABLED mode, quickest way to test):**
```bash
-java -jar zosmf-jwt-check-.jar \
+java -jar certificate-analyser/build/libs/certificate-analyser-.jar --zosmf-jwt-check \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
--verify-certificates DISABLED
@@ -67,7 +79,7 @@ java -jar zosmf-jwt-check-.jar \
**Full example (STRICT mode with truststore):**
```bash
-java -jar zosmf-jwt-check-.jar \
+java -jar certificate-analyser/build/libs/certificate-analyser-.jar --zosmf-jwt-check \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
--truststore-file /path/to/truststore.p12 \
@@ -77,7 +89,7 @@ java -jar zosmf-jwt-check-.jar \
**Display help:**
```bash
-java -jar zosmf-jwt-check-.jar --help
+java -jar certificate-analyser/build/libs/certificate-analyser-.jar --zosmf-jwt-check --help
```
## CLI Flags Reference
@@ -192,7 +204,7 @@ Below are step-by-step commands for testing all modes. Replace `` with
The fastest way to verify basic TCP + HTTP connectivity:
```bash
-java -jar zosmf-jwt-check/build/libs/zosmf-jwt-check-.jar \
+java -jar certificate-analyser/build/libs/certificate-analyser-.jar --zosmf-jwt-check \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
--verify-certificates DISABLED
@@ -211,7 +223,7 @@ SUCCESS: z/OSMF JWK endpoint exists (returned 401 Unauthorized — expected with
Requires a truststore containing the z/OSMF server's CA certificate (see [Creating a Truststore](#creating-a-truststore)):
```bash
-java -jar zosmf-jwt-check/build/libs/zosmf-jwt-check-.jar \
+java -jar certificate-analyser/build/libs/certificate-analyser-.jar --zosmf-jwt-check \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
--truststore-file /path/to/zosmf-truststore.p12 \
@@ -238,7 +250,7 @@ Details: PKIX path building failed: ...unable to find valid certification path t
Useful when connecting via IP address but the certificate has a DNS name:
```bash
-java -jar zosmf-jwt-check/build/libs/zosmf-jwt-check-.jar \
+java -jar certificate-analyser/build/libs/certificate-analyser-.jar --zosmf-jwt-check \
--zosmf-host 10.0.0.50 \
--zosmf-port 11443 \
--truststore-file /path/to/zosmf-truststore.p12 \
@@ -259,56 +271,19 @@ SUCCESS: z/OSMF JWK endpoint exists (returned 401 Unauthorized — expected with
For z/OSMF instances running on plain HTTP (uncommon):
```bash
-java -jar zosmf-jwt-check/build/libs/zosmf-jwt-check-.jar \
+java -jar certificate-analyser/build/libs/certificate-analyser-.jar --zosmf-jwt-check \
--zosmf-host myzosmf.example.com \
--zosmf-port 80 \
--scheme http
```
-### 5. Validation Error Tests
-
-**Missing required flags:**
-
-```bash
-# No arguments at all
-java -jar zosmf-jwt-check-.jar
-# Output: Missing required options: '--zosmf-host=', '--zosmf-port='
-
-# Missing truststore in STRICT mode
-java -jar zosmf-jwt-check-.jar --zosmf-host myhost --zosmf-port 443
-# Output: ERROR: --truststore-file is required when --scheme=https and verification is not DISABLED.
-
-# Missing truststore password
-java -jar zosmf-jwt-check-.jar --zosmf-host myhost --zosmf-port 443 --truststore-file my.p12
-# Output: ERROR: --truststore-password is required when --scheme=https and verification is not DISABLED.
-```
-
-**Invalid values:**
-
-```bash
-# Invalid scheme
-java -jar zosmf-jwt-check-.jar --zosmf-host myhost --zosmf-port 443 --scheme ftp
-# Output: ERROR: --scheme must be 'http' or 'https', got: ftp
-
-# Invalid verify mode
-java -jar zosmf-jwt-check-.jar --zosmf-host myhost --zosmf-port 443 --verify-certificates INVALID
-# Output: ERROR: --verify-certificates must be STRICT, NONSTRICT, or DISABLED, got: INVALID
-```
-
-**Unreachable host:**
-
-```bash
-java -jar zosmf-jwt-check-.jar --zosmf-host nonexistent.host --zosmf-port 443 --verify-certificates DISABLED
-# Output: FAILURE: Cannot connect to nonexistent.host:443.
-```
-
## SAF Keyrings
On z/OS, if you are using SAF keyrings instead of file-based keystores/truststores, provide the keyring path in the `safkeyring://` format and add the IBM crypto modules to the JVM module graph:
```bash
java --add-modules ibm.crypto.zsecurity,ibm.crypto.hdwrcca \
- -jar zosmf-jwt-check-.jar \
+ -jar certificate-analyser/build/libs/certificate-analyser-.jar --zosmf-jwt-check \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
--truststore-file safkeyring://IZUSVR/ZoweKeyring \
diff --git a/zosmf-jwt-check/build.gradle b/zosmf-jwt-check/build.gradle
index e95188ccdf..8a6d9a333d 100644
--- a/zosmf-jwt-check/build.gradle
+++ b/zosmf-jwt-check/build.gradle
@@ -15,13 +15,6 @@ compileJava {
}
jar {
- manifest {
- attributes(
- 'Main-Class': 'org.zowe.apiml.ZosmfJwtCheck'
- )
- }
- duplicatesStrategy = DuplicatesStrategy.EXCLUDE
- from {
- configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
- }
+ // Thin JAR — consumed as a dependency by certificate-analyser's fat JAR.
+ // No Main-Class manifest; not intended to be run standalone.
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
index 8e4f8cedaf..889ac23147 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
@@ -23,17 +23,17 @@
/**
* Builds a TLSv1.2 {@link SSLContext} in two modes:
*
- * - {@link #initSSLContext(Stores)} — normal mode using real truststore/keystore
+ * - {@link #initSSLContext(ZosmfStores)} — normal mode using real truststore/keystore
* - {@link #initTrustAllSSLContext()} — trust-all mode for DISABLED verification
*
*/
@SuppressWarnings("squid:S106")
public class SSLContextFactory {
- private final Stores stores;
+ private final ZosmfStores stores;
private SSLContext sslContext;
- private SSLContextFactory(Stores stores) {
+ private SSLContextFactory(ZosmfStores stores) {
this.stores = stores;
}
@@ -47,7 +47,7 @@ public SSLContext getSslContext() {
* @param stores loaded keystore/truststore pair
* @return factory holding the initialized SSLContext
*/
- public static SSLContextFactory initSSLContext(Stores stores) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException, CertificateException, IOException {
+ public static SSLContextFactory initSSLContext(ZosmfStores stores) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException, CertificateException, IOException {
SSLContextFactory factory = new SSLContextFactory(stores);
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
index 6fb4619f3a..69728b01c4 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
@@ -53,7 +53,7 @@ public static int mainWithExitCode(String[] args) {
HostnameVerifier noopVerifier = (hostname, session) -> true;
httpClient = new HttpClientWrapper(sslContextFactory.getSslContext(), noopVerifier);
} else {
- Stores stores = new Stores(conf);
+ ZosmfStores stores = new ZosmfStores(conf);
SSLContextFactory sslContextFactory = SSLContextFactory.initSSLContext(stores);
HostnameVerifier hostnameVerifier;
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfStores.java
similarity index 98%
rename from zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
rename to zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfStores.java
index 6d78e27f49..d393a5d395 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/Stores.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfStores.java
@@ -28,7 +28,7 @@
* or z/OS SAF keyrings. Supports PKCS12, JKS, and {@code safkeyring://} URIs.
*/
@SuppressWarnings("squid:S106")
-public class Stores {
+public class ZosmfStores {
private static final Pattern KEYRING_PATTERN = Pattern.compile("^(safkeyring[^:]*):/{2,4}([^/]+)/([^/]+)$");
@@ -36,7 +36,7 @@ public class Stores {
private KeyStore trustStore;
private final ZosmfJwtCheckConfig conf;
- public Stores(ZosmfJwtCheckConfig conf) {
+ public ZosmfStores(ZosmfJwtCheckConfig conf) {
this.conf = conf;
init();
}
From 3a513f3eb2498d5a5d1ee33451e3dedb00db25f4 Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Tue, 2 Jun 2026 15:36:11 +0530
Subject: [PATCH 24/28] refinements
Signed-off-by: hrishikesh-nalawade
---
.../org/zowe/apiml/JwkEndpointChecker.java | 21 +++++++-----
.../org/zowe/apiml/SSLContextFactory.java | 8 ++---
.../java/org/zowe/apiml/ZosmfJwtCheck.java | 9 ++---
.../zowe/apiml/JwkEndpointCheckerTest.java | 33 +++++++------------
4 files changed, 31 insertions(+), 40 deletions(-)
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
index 40a1afd56d..84fa29c635 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/JwkEndpointChecker.java
@@ -29,6 +29,9 @@ public class JwkEndpointChecker {
static final String JWK_ENDPOINT_PATH = "/jwt/ibm/api/zOSMFBuilder/jwk";
private static final String ZOSMF_CSRF_HEADER = "X-CSRF-ZOSMF-HEADER";
+ private static final String PREFIX_RESPONSE_BODY = "Response body: ";
+ private static final String PREFIX_URL = "URL: ";
+ private static final String PREFIX_DETAILS = "Details: ";
private static final Pattern N_VALUE_PATTERN = Pattern.compile("\"n\"\\s*:\\s*\"([^\"]*)\"");
private final HttpClientWrapper httpClient;
@@ -58,12 +61,12 @@ public boolean check() {
} catch (SSLHandshakeException e) {
System.err.println("FAILURE: SSL handshake failed when connecting to " + urlString + ".");
System.err.println("Verify that the truststore contains the z/OSMF server certificate.");
- System.err.println("Details: " + e.getMessage());
+ System.err.println(PREFIX_DETAILS + e.getMessage());
return false;
} catch (ConnectException e) {
System.err.println("FAILURE: Cannot connect to " + conf.getZosmfHost() + ":" + conf.getZosmfPort() + ".");
System.err.println("Verify the host and port are correct and z/OSMF is running.");
- System.err.println("Details: " + e.getMessage());
+ System.err.println(PREFIX_DETAILS + e.getMessage());
return false;
} catch (SocketTimeoutException e) {
System.err.println("FAILURE: Connection timed out to " + conf.getZosmfHost() + ":" + conf.getZosmfPort() + ".");
@@ -76,7 +79,7 @@ public boolean check() {
return false;
} catch (Exception e) {
System.err.println("FAILURE: Error when calling " + urlString + " verify hostname and port.");
- System.err.println("Details: " + e.getMessage());
+ System.err.println(PREFIX_DETAILS + e.getMessage());
return false;
}
}
@@ -103,25 +106,25 @@ boolean evaluateResponseCode(int responseCode, String body, String urlString) {
if (responseCode >= 400 && responseCode < 500) {
System.err.println("FAILURE: z/OSMF JWK endpoint returned unexpected client error. HTTP " + responseCode);
- System.err.println("URL: " + urlString);
+ System.err.println(PREFIX_URL + urlString);
return false;
}
if (responseCode >= 500) {
System.err.println("FAILURE: z/OSMF JWK endpoint returned server error. HTTP " + responseCode);
- System.err.println("URL: " + urlString);
+ System.err.println(PREFIX_URL + urlString);
return false;
}
System.err.println("FAILURE: z/OSMF JWK endpoint returned unexpected response code. HTTP " + responseCode);
- System.err.println("URL: " + urlString);
+ System.err.println(PREFIX_URL + urlString);
return false;
}
boolean validateJwkBody(String body) {
if (body == null || body.isEmpty()) {
System.err.println("WARNING: z/OSMF JWK endpoint returned an empty response body.");
- System.err.println("Response body: " + (body == null ? "" : ""));
+ System.err.println(PREFIX_RESPONSE_BODY + (body == null ? "" : ""));
return false;
}
@@ -129,7 +132,7 @@ boolean validateJwkBody(String body) {
if (!matcher.find()) {
System.err.println("WARNING: JWK response does not contain an RSA modulus (\"n\" key).");
System.err.println("The z/OSMF JWK endpoint may not be properly configured.");
- System.err.println("Response body: " + body);
+ System.err.println(PREFIX_RESPONSE_BODY + body);
return false;
}
@@ -138,7 +141,7 @@ boolean validateJwkBody(String body) {
System.err.println("FAILURE: JWK response contains an empty RSA modulus (\"n\" key is empty).");
System.err.println("The z/OSMF server returned a key that cannot be used for JWT verification.");
System.err.println("Check z/OSMF JWT configuration and ensure the signing key is properly generated.");
- System.err.println("Response body: " + body);
+ System.err.println(PREFIX_RESPONSE_BODY + body);
return false;
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
index 889ac23147..1044672d2f 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/SSLContextFactory.java
@@ -30,11 +30,9 @@
@SuppressWarnings("squid:S106")
public class SSLContextFactory {
- private final ZosmfStores stores;
private SSLContext sslContext;
- private SSLContextFactory(ZosmfStores stores) {
- this.stores = stores;
+ private SSLContextFactory() {
}
public SSLContext getSslContext() {
@@ -48,7 +46,7 @@ public SSLContext getSslContext() {
* @return factory holding the initialized SSLContext
*/
public static SSLContextFactory initSSLContext(ZosmfStores stores) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException, CertificateException, IOException {
- SSLContextFactory factory = new SSLContextFactory(stores);
+ SSLContextFactory factory = new SSLContextFactory();
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(stores.getTrustStore());
@@ -73,7 +71,7 @@ public static SSLContextFactory initSSLContext(ZosmfStores stores) throws NoSuch
* @return factory holding the trust-all SSLContext
*/
public static SSLContextFactory initTrustAllSSLContext() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, CertificateException, IOException, UnrecoverableKeyException {
- SSLContextFactory factory = new SSLContextFactory(null);
+ SSLContextFactory factory = new SSLContextFactory();
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
index 69728b01c4..e4bbcf3963 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
@@ -24,6 +24,7 @@
@SuppressWarnings("squid:S106")
public class ZosmfJwtCheck {
+ static final String SCHEME_HTTPS = "https";
static final String VERIFY_STRICT = "STRICT";
static final String VERIFY_NONSTRICT = "NONSTRICT";
static final String VERIFY_DISABLED = "DISABLED";
@@ -45,7 +46,7 @@ public static int mainWithExitCode(String[] args) {
validateConfig(conf);
HttpClientWrapper httpClient;
- if ("https".equalsIgnoreCase(conf.getScheme())) {
+ if (SCHEME_HTTPS.equalsIgnoreCase(conf.getScheme())) {
String verifyMode = conf.getVerifyCertificates().toUpperCase();
if (VERIFY_DISABLED.equals(verifyMode)) {
@@ -80,7 +81,7 @@ public static int mainWithExitCode(String[] args) {
static void validateConfig(ZosmfJwtCheckConf conf) {
String scheme = conf.getScheme();
- if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) {
+ if (!"http".equalsIgnoreCase(scheme) && !SCHEME_HTTPS.equalsIgnoreCase(scheme)) {
throw new IllegalArgumentException("--scheme must be 'http' or 'https', got: " + scheme);
}
@@ -89,7 +90,7 @@ static void validateConfig(ZosmfJwtCheckConf conf) {
throw new IllegalArgumentException("--verify-certificates must be STRICT, NONSTRICT, or DISABLED, got: " + conf.getVerifyCertificates());
}
- if ("https".equalsIgnoreCase(scheme) && !VERIFY_DISABLED.equals(verifyMode)) {
+ if (SCHEME_HTTPS.equalsIgnoreCase(scheme) && !VERIFY_DISABLED.equals(verifyMode)) {
if (conf.getTrustStore() == null) {
throw new IllegalArgumentException("--truststore-file is required when --scheme=https and verification is not DISABLED. " +
"Provide the path to the truststore containing the z/OSMF server certificate.");
@@ -119,7 +120,7 @@ static void ensureSafkeyringHandler() {
StringBuilder sb = new StringBuilder(existing);
for (String prefix : packagePrefixes) {
if (!existing.contains(prefix)) {
- if (sb.length() > 0) {
+ if (!sb.isEmpty()) {
sb.append('|');
}
sb.append(prefix);
diff --git a/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java b/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
index 7e30a847d8..8e18228afb 100644
--- a/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
+++ b/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
@@ -14,6 +14,8 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import javax.net.ssl.SSLHandshakeException;
import java.io.ByteArrayOutputStream;
@@ -90,31 +92,18 @@ void response200WithEmptyModulusIsFailure() throws IOException {
@Nested
class FailureResponses {
- @Test
- void response404IsFailure() throws IOException {
- when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(404, ""));
- JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
- assertFalse(checker.check());
- assertTrue(errStream.toString().contains("FAILURE"));
- assertTrue(errStream.toString().contains("404"));
- }
-
- @Test
- void response500IsFailure() throws IOException {
- when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(500, ""));
- JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
- assertFalse(checker.check());
- assertTrue(errStream.toString().contains("FAILURE"));
- assertTrue(errStream.toString().contains("server error"));
- }
-
- @Test
- void response403IsFailure() throws IOException {
- when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(403, ""));
+ @ParameterizedTest
+ @CsvSource({
+ "404, 404",
+ "500, server error",
+ "403, client error"
+ })
+ void failureResponseCodesAreReported(int statusCode, String expectedMessage) throws IOException {
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(statusCode, ""));
JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
assertFalse(checker.check());
assertTrue(errStream.toString().contains("FAILURE"));
- assertTrue(errStream.toString().contains("client error"));
+ assertTrue(errStream.toString().contains(expectedMessage));
}
}
From f603478801adb670e646f6d2a0ac034ea0c19a44 Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Wed, 3 Jun 2026 16:06:07 +0530
Subject: [PATCH 25/28] Removed code duplication and added tests
Signed-off-by: hrishikesh-nalawade
---
certificate-analyser/build.gradle | 3 +-
.../main/java/org/zowe/apiml/Analyser.java | 30 +--
.../main/java/org/zowe/apiml/ApimlConf.java | 7 +-
.../src/main/java/org/zowe/apiml/Stores.java | 62 +----
.../apiml/StoresNotInitializeException.java | 18 --
.../java/org/zowe/apiml/AnalyserTest.java | 6 +-
.../test/java/org/zowe/apiml/StoresTest.java | 26 +-
certificate-common/build.gradle | 8 +
.../org/zowe/apiml/common/KeyringUtils.java | 129 ++++++++++
.../common}/StoresNotInitializeException.java | 2 +-
.../zowe/apiml/common/KeyringUtilsTest.java | 224 ++++++++++++++++++
settings.gradle | 1 +
zosmf-jwt-check/build.gradle | 1 +
.../java/org/zowe/apiml/ZosmfJwtCheck.java | 30 +--
.../main/java/org/zowe/apiml/ZosmfStores.java | 73 +-----
.../org/zowe/apiml/HttpClientWrapperTest.java | 162 +++++++++++++
.../org/zowe/apiml/SSLContextFactoryTest.java | 112 +++++++++
.../java/org/zowe/apiml/ZosmfStoresTest.java | 183 ++++++++++++++
18 files changed, 870 insertions(+), 207 deletions(-)
delete mode 100644 certificate-analyser/src/main/java/org/zowe/apiml/StoresNotInitializeException.java
create mode 100644 certificate-common/build.gradle
create mode 100644 certificate-common/src/main/java/org/zowe/apiml/common/KeyringUtils.java
rename {zosmf-jwt-check/src/main/java/org/zowe/apiml => certificate-common/src/main/java/org/zowe/apiml/common}/StoresNotInitializeException.java (95%)
create mode 100644 certificate-common/src/test/java/org/zowe/apiml/common/KeyringUtilsTest.java
create mode 100644 zosmf-jwt-check/src/test/java/org/zowe/apiml/HttpClientWrapperTest.java
create mode 100644 zosmf-jwt-check/src/test/java/org/zowe/apiml/SSLContextFactoryTest.java
create mode 100644 zosmf-jwt-check/src/test/java/org/zowe/apiml/ZosmfStoresTest.java
diff --git a/certificate-analyser/build.gradle b/certificate-analyser/build.gradle
index d68085d549..82ca902b79 100644
--- a/certificate-analyser/build.gradle
+++ b/certificate-analyser/build.gradle
@@ -3,6 +3,7 @@ plugins {
}
dependencies {
+ implementation project(':certificate-common')
implementation project(':zosmf-jwt-check')
implementation libs.picocli
annotationProcessor libs.picocli.codegen
@@ -17,7 +18,7 @@ compileJava {
}
jar {
- dependsOn ':zosmf-jwt-check:jar'
+ dependsOn ':certificate-common:jar', ':zosmf-jwt-check:jar'
manifest {
attributes(
'Main-Class': 'org.zowe.apiml.Analyser'
diff --git a/certificate-analyser/src/main/java/org/zowe/apiml/Analyser.java b/certificate-analyser/src/main/java/org/zowe/apiml/Analyser.java
index 6a35869898..89dbda5612 100644
--- a/certificate-analyser/src/main/java/org/zowe/apiml/Analyser.java
+++ b/certificate-analyser/src/main/java/org/zowe/apiml/Analyser.java
@@ -22,7 +22,7 @@ public class Analyser {
private static final String ZOSMF_JWT_CHECK_FLAG = "--zosmf-jwt-check";
public static int mainWithExitCode(String[] args) {
- ensureSafkeyringHandler();
+ org.zowe.apiml.common.KeyringUtils.ensureSafkeyringHandler();
if (args.length > 0 && ZOSMF_JWT_CHECK_FLAG.equals(args[0])) {
String[] remainingArgs = Arrays.copyOfRange(args, 1, args.length);
@@ -72,34 +72,6 @@ public static int mainWithExitCode(String[] args) {
return 4;
}
- /**
- * Registers IBM SAF keyring URL protocol handler packages via the
- * {@code java.protocol.handler.pkgs} system property.
- *
- * On IBM Java 17/21 (z/OS), this property works in conjunction with
- * {@code --add-modules ibm.crypto.zsecurity,ibm.crypto.hdwrcca} to enable
- * the {@code safkeyring://} URL protocol. The {@code --add-modules} flag
- * resolves the module (making classes accessible), while this property tells
- * the {@link java.net.URL} class which packages to search for the handler.
- */
- static void ensureSafkeyringHandler() {
- String[] packagePrefixes = {
- "com.ibm.crypto.zsecurity.provider",
- "com.ibm.crypto.hdwrCCA.provider"
- };
- String existing = System.getProperty("java.protocol.handler.pkgs", "");
- StringBuilder sb = new StringBuilder(existing);
- for (String prefix : packagePrefixes) {
- if (!existing.contains(prefix)) {
- if (!sb.isEmpty()) {
- sb.append('|');
- }
- sb.append(prefix);
- }
- }
- System.setProperty("java.protocol.handler.pkgs", sb.toString());
- }
-
public static final void main(String[] args) {
System.exit(mainWithExitCode(args));
}
diff --git a/certificate-analyser/src/main/java/org/zowe/apiml/ApimlConf.java b/certificate-analyser/src/main/java/org/zowe/apiml/ApimlConf.java
index ceafb37fdc..65885decb0 100644
--- a/certificate-analyser/src/main/java/org/zowe/apiml/ApimlConf.java
+++ b/certificate-analyser/src/main/java/org/zowe/apiml/ApimlConf.java
@@ -16,7 +16,12 @@
@CommandLine.Command(version = {
"Versioned Command 1.0",
"JVM: ${java.version} (${java.vendor} ${java.vm.name} ${java.vm.version})",
- "OS: ${os.name} ${os.version} ${os.arch}"})
+ "OS: ${os.name} ${os.version} ${os.arch}"},
+ footer = {
+ "",
+ "Additional commands:",
+ " --zosmf-jwt-check Run the z/OSMF JWT validation check.",
+ " Use --zosmf-jwt-check --help for details."})
public class ApimlConf implements Config {
@Option(names = {"-k", "--keystore"}, description = "Path to keystore file or keyring. When using keyring, pass -Djava.protocol.handler.pkgs=com.ibm.crypto.provider in command line.")
diff --git a/certificate-analyser/src/main/java/org/zowe/apiml/Stores.java b/certificate-analyser/src/main/java/org/zowe/apiml/Stores.java
index 3cc4afd108..7f5003daaf 100644
--- a/certificate-analyser/src/main/java/org/zowe/apiml/Stores.java
+++ b/certificate-analyser/src/main/java/org/zowe/apiml/Stores.java
@@ -10,12 +10,13 @@
package org.zowe.apiml;
+import org.zowe.apiml.common.KeyringUtils;
+import org.zowe.apiml.common.StoresNotInitializeException;
+
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
@@ -25,14 +26,10 @@
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
@SuppressWarnings("squid:S106")
public class Stores {
- private static final Pattern KEYRING_PATTERN = Pattern.compile("^(safkeyring[^:]*):/{2,4}([^/]+)/([^/]+)$");
-
private KeyStore keyStore;
private KeyStore trustStore;
private final Config conf;
@@ -43,21 +40,6 @@ public Stores(Config conf) {
init();
}
- public static boolean isKeyring(String input) {
- if (input == null) return false;
- Matcher matcher = KEYRING_PATTERN.matcher(input);
- return matcher.matches();
- }
-
- public static String formatKeyringUrl(String input) {
- if (input == null) return null;
- Matcher matcher = KEYRING_PATTERN.matcher(input);
- if (matcher.matches()) {
- return matcher.group(1) + "://" + matcher.group(2) + "/" + matcher.group(3);
- }
- return input;
- }
-
void init() {
try {
initKeystore();
@@ -82,13 +64,13 @@ private void initTruststore() throws IOException, CertificateException, NoSuchAl
}
return;
}
- if (isKeyring(conf.getTrustStore())) {
- try (InputStream trustStoreIStream = keyRingUrl(conf.getTrustStore()).openStream()) {
- this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustPasswd().toCharArray(), conf.getTrustStoreType());
+ if (KeyringUtils.isKeyring(conf.getTrustStore())) {
+ try (InputStream trustStoreIStream = KeyringUtils.keyRingUrl(conf.getTrustStore()).openStream()) {
+ this.trustStore = KeyringUtils.readKeyStore(trustStoreIStream, conf.getTrustPasswd().toCharArray(), conf.getTrustStoreType());
}
} else {
try (InputStream trustStoreIStream = new FileInputStream(conf.getTrustStore())) {
- this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustPasswd().toCharArray(), conf.getTrustStoreType());
+ this.trustStore = KeyringUtils.readKeyStore(trustStoreIStream, conf.getTrustPasswd().toCharArray(), conf.getTrustStoreType());
}
}
@@ -104,16 +86,16 @@ private void initKeystore() throws IOException, CertificateException, NoSuchAlgo
}
return;
}
- if (isKeyring(conf.getKeyStore())) {
- try (InputStream keyringIStream = keyRingUrl(conf.getKeyStore()).openStream()) {
- this.keyStore = readKeyStore(keyringIStream, conf.getKeyPasswd().toCharArray(), conf.getKeyStoreType());
+ if (KeyringUtils.isKeyring(conf.getKeyStore())) {
+ try (InputStream keyringIStream = KeyringUtils.keyRingUrl(conf.getKeyStore()).openStream()) {
+ this.keyStore = KeyringUtils.readKeyStore(keyringIStream, conf.getKeyPasswd().toCharArray(), conf.getKeyStoreType());
this.trustStore = this.keyStore;
} catch (Exception e) {
throw new StoresNotInitializeException(e.getMessage());
}
} else {
try (InputStream keyStoreIStream = new FileInputStream(conf.getKeyStore())) {
- this.keyStore = readKeyStore(keyStoreIStream, conf.getKeyPasswd().toCharArray(), conf.getKeyStoreType());
+ this.keyStore = KeyringUtils.readKeyStore(keyStoreIStream, conf.getKeyPasswd().toCharArray(), conf.getKeyStoreType());
}
}
}
@@ -149,12 +131,6 @@ public Certificate[] getServerCertificateChain(String alias) throws KeyStoreExce
return keyStore.getCertificateChain(alias);
}
- public static KeyStore readKeyStore(InputStream is, char[] pass, String type) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
- KeyStore keyStore = KeyStore.getInstance(type);
- keyStore.load(is, pass);
- return keyStore;
- }
-
public KeyStore getKeyStore() {
return keyStore;
}
@@ -166,20 +142,4 @@ public KeyStore getTrustStore() {
public Config getConf() {
return conf;
}
-
- public static URL keyRingUrl(String uri) throws MalformedURLException {
- if (!isKeyring(uri)) {
- throw new StoresNotInitializeException("Incorrect key ring format: " + uri
- + ". Make sure you use format safkeyring://userId/keyRing");
- }
- String formatted = formatKeyringUrl(uri);
- try {
- return new URL(formatted);
- } catch (MalformedURLException e) {
- System.err.println("ERROR: Unknown protocol in '" + formatted + "': " + e.getMessage());
- System.err.println("Ensure the JVM is started with: --add-modules ibm.crypto.zsecurity,ibm.crypto.hdwrcca");
- System.err.println("And that ensureSafkeyringHandler() has been called to set java.protocol.handler.pkgs");
- throw e;
- }
- }
}
diff --git a/certificate-analyser/src/main/java/org/zowe/apiml/StoresNotInitializeException.java b/certificate-analyser/src/main/java/org/zowe/apiml/StoresNotInitializeException.java
deleted file mode 100644
index e5c77f773c..0000000000
--- a/certificate-analyser/src/main/java/org/zowe/apiml/StoresNotInitializeException.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * This program and the accompanying materials are made available under the terms of the
- * Eclipse Public License v2.0 which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-v20.html
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Copyright Contributors to the Zowe Project.
- */
-
-package org.zowe.apiml;
-
-public class StoresNotInitializeException extends RuntimeException {
-
- public StoresNotInitializeException(String message) {
- super(message);
- }
-}
diff --git a/certificate-analyser/src/test/java/org/zowe/apiml/AnalyserTest.java b/certificate-analyser/src/test/java/org/zowe/apiml/AnalyserTest.java
index 96e36e0a89..e667aee414 100644
--- a/certificate-analyser/src/test/java/org/zowe/apiml/AnalyserTest.java
+++ b/certificate-analyser/src/test/java/org/zowe/apiml/AnalyserTest.java
@@ -128,7 +128,7 @@ void restoreProperty() {
@Test
void whenPropertyNotSet_thenBothPackagesAreRegistered() {
System.clearProperty("java.protocol.handler.pkgs");
- Analyser.ensureSafkeyringHandler();
+ org.zowe.apiml.common.KeyringUtils.ensureSafkeyringHandler();
String value = System.getProperty("java.protocol.handler.pkgs");
assertTrue(value.contains("com.ibm.crypto.zsecurity.provider"));
assertTrue(value.contains("com.ibm.crypto.hdwrCCA.provider"));
@@ -137,7 +137,7 @@ void whenPropertyNotSet_thenBothPackagesAreRegistered() {
@Test
void whenPropertyAlreadyHasOtherPackages_thenNewPackagesAreAppended() {
System.setProperty("java.protocol.handler.pkgs", "com.example.custom");
- Analyser.ensureSafkeyringHandler();
+ org.zowe.apiml.common.KeyringUtils.ensureSafkeyringHandler();
String value = System.getProperty("java.protocol.handler.pkgs");
assertTrue(value.startsWith("com.example.custom|"));
assertTrue(value.contains("com.ibm.crypto.zsecurity.provider"));
@@ -148,7 +148,7 @@ void whenPropertyAlreadyHasOtherPackages_thenNewPackagesAreAppended() {
void whenPropertyAlreadyContainsPackages_thenNoDuplicatesAdded() {
System.setProperty("java.protocol.handler.pkgs",
"com.ibm.crypto.zsecurity.provider|com.ibm.crypto.hdwrCCA.provider");
- Analyser.ensureSafkeyringHandler();
+ org.zowe.apiml.common.KeyringUtils.ensureSafkeyringHandler();
String value = System.getProperty("java.protocol.handler.pkgs");
assertEquals("com.ibm.crypto.zsecurity.provider|com.ibm.crypto.hdwrCCA.provider", value);
}
diff --git a/certificate-analyser/src/test/java/org/zowe/apiml/StoresTest.java b/certificate-analyser/src/test/java/org/zowe/apiml/StoresTest.java
index 9cd6468196..da5abc3bea 100644
--- a/certificate-analyser/src/test/java/org/zowe/apiml/StoresTest.java
+++ b/certificate-analyser/src/test/java/org/zowe/apiml/StoresTest.java
@@ -12,6 +12,8 @@
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import org.zowe.apiml.common.KeyringUtils;
+import org.zowe.apiml.common.StoresNotInitializeException;
import picocli.CommandLine;
import java.net.MalformedURLException;
@@ -95,40 +97,40 @@ void whenTruststoreIsKeyring_thenKeyRingUrlIsUsed() {
@Test
void givenValidKeyringUri_whenProtocolHandlerNotAvailable_thenThrowsMalformedURLException() {
- assertThrows(MalformedURLException.class, () -> Stores.keyRingUrl("safkeyring://userId/keyRing"));
+ assertThrows(MalformedURLException.class, () -> KeyringUtils.keyRingUrl("safkeyring://userId/keyRing"));
}
@Test
void whenKeyRingUrlCalledWithInvalidFormat_thenThrowsStoresNotInitializeException() {
StoresNotInitializeException e = assertThrows(StoresNotInitializeException.class,
- () -> Stores.keyRingUrl("notakeyring://bad/format/extra"));
+ () -> KeyringUtils.keyRingUrl("notakeyring://bad/format/extra"));
assertTrue(e.getMessage().contains("Incorrect key ring format"));
}
@Test
void givenIsKeyring_whenValidSafkeyringUri_thenReturnsTrue() {
- assertTrue(Stores.isKeyring("safkeyring://userId/keyRing"));
- assertTrue(Stores.isKeyring("safkeyring:////userId/keyRing"));
- assertTrue(Stores.isKeyring("safkeyringce://userId/keyRing"));
+ assertTrue(KeyringUtils.isKeyring("safkeyring://userId/keyRing"));
+ assertTrue(KeyringUtils.isKeyring("safkeyring:////userId/keyRing"));
+ assertTrue(KeyringUtils.isKeyring("safkeyringce://userId/keyRing"));
}
@Test
void givenIsKeyring_whenInvalidUri_thenReturnsFalse() {
- assertFalse(Stores.isKeyring(null));
- assertFalse(Stores.isKeyring(""));
- assertFalse(Stores.isKeyring("/path/to/file.p12"));
- assertFalse(Stores.isKeyring("keyring://userId/keyRing"));
+ assertFalse(KeyringUtils.isKeyring(null));
+ assertFalse(KeyringUtils.isKeyring(""));
+ assertFalse(KeyringUtils.isKeyring("/path/to/file.p12"));
+ assertFalse(KeyringUtils.isKeyring("keyring://userId/keyRing"));
}
@Test
void givenFormatKeyringUrl_whenUriHasExtraSlashes_thenNormalized() {
- assertEquals("safkeyring://userId/keyRing", Stores.formatKeyringUrl("safkeyring:////userId/keyRing"));
- assertEquals("safkeyring://userId/keyRing", Stores.formatKeyringUrl("safkeyring://userId/keyRing"));
+ assertEquals("safkeyring://userId/keyRing", KeyringUtils.formatKeyringUrl("safkeyring:////userId/keyRing"));
+ assertEquals("safkeyring://userId/keyRing", KeyringUtils.formatKeyringUrl("safkeyring://userId/keyRing"));
}
@Test
void givenFormatKeyringUrl_whenNull_thenReturnsNull() {
- assertNull(Stores.formatKeyringUrl(null));
+ assertNull(KeyringUtils.formatKeyringUrl(null));
}
}
diff --git a/certificate-common/build.gradle b/certificate-common/build.gradle
new file mode 100644
index 0000000000..d5aeef933c
--- /dev/null
+++ b/certificate-common/build.gradle
@@ -0,0 +1,8 @@
+plugins {
+ id 'java'
+}
+
+dependencies {
+ testImplementation libs.mockito.core
+ testImplementation libs.hamcrest
+}
diff --git a/certificate-common/src/main/java/org/zowe/apiml/common/KeyringUtils.java b/certificate-common/src/main/java/org/zowe/apiml/common/KeyringUtils.java
new file mode 100644
index 0000000000..52b7a1f12f
--- /dev/null
+++ b/certificate-common/src/main/java/org/zowe/apiml/common/KeyringUtils.java
@@ -0,0 +1,129 @@
+/*
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Copyright Contributors to the Zowe Project.
+ */
+
+package org.zowe.apiml.common;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Shared utilities for SAF keyring URI handling and KeyStore loading.
+ * Used by both {@code certificate-analyser} and {@code zosmf-jwt-check}.
+ */
+@SuppressWarnings("squid:S106")
+public final class KeyringUtils {
+
+ private static final Pattern KEYRING_PATTERN = Pattern.compile("^(safkeyring[^:]*):/{2,4}([^/]+)/([^/]+)$");
+
+ private KeyringUtils() {
+ // utility class
+ }
+
+ /**
+ * Checks whether the given path is a SAF keyring URI.
+ *
+ * @param input store path to check
+ * @return {@code true} if the path matches the keyring pattern
+ */
+ public static boolean isKeyring(String input) {
+ if (input == null) return false;
+ Matcher matcher = KEYRING_PATTERN.matcher(input);
+ return matcher.matches();
+ }
+
+ /**
+ * Normalizes a keyring URI to the canonical {@code safkeyring://userId/keyRing} format.
+ *
+ * @param input raw keyring URI
+ * @return normalized URI, or the original input if not a keyring
+ */
+ public static String formatKeyringUrl(String input) {
+ if (input == null) return null;
+ Matcher matcher = KEYRING_PATTERN.matcher(input);
+ if (matcher.matches()) {
+ return matcher.group(1) + "://" + matcher.group(2) + "/" + matcher.group(3);
+ }
+ return input;
+ }
+
+ /**
+ * Converts a keyring URI string to a {@link URL} suitable for opening as a stream.
+ *
+ * @param uri the keyring URI
+ * @return URL object
+ * @throws MalformedURLException if the URI is invalid
+ * @throws StoresNotInitializeException if the URI does not match keyring format
+ */
+ public static URL keyRingUrl(String uri) throws MalformedURLException {
+ if (!isKeyring(uri)) {
+ throw new StoresNotInitializeException("Incorrect key ring format: " + uri
+ + ". Make sure you use format safkeyring://userId/keyRing");
+ }
+ String formatted = formatKeyringUrl(uri);
+ try {
+ return new URL(formatted);
+ } catch (MalformedURLException e) {
+ System.err.println("ERROR: Unknown protocol in '" + formatted + "': " + e.getMessage());
+ System.err.println("Ensure the JVM is started with: --add-modules ibm.crypto.zsecurity,ibm.crypto.hdwrcca");
+ System.err.println("And that ensureSafkeyringHandler() has been called to set java.protocol.handler.pkgs");
+ throw e;
+ }
+ }
+
+ /**
+ * Loads a {@link KeyStore} from an input stream.
+ *
+ * @param is input stream to read from
+ * @param pass keystore password
+ * @param type keystore type (e.g. PKCS12, JKS)
+ * @return loaded KeyStore instance
+ */
+ public static KeyStore readKeyStore(InputStream is, char[] pass, String type) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
+ KeyStore keyStore = KeyStore.getInstance(type);
+ keyStore.load(is, pass);
+ return keyStore;
+ }
+
+ /**
+ * Registers IBM SAF keyring URL protocol handler packages via the
+ * {@code java.protocol.handler.pkgs} system property.
+ *
+ * On IBM Java 17/21 (z/OS), this property works in conjunction with
+ * {@code --add-modules ibm.crypto.zsecurity,ibm.crypto.hdwrcca} to enable
+ * the {@code safkeyring://} URL protocol. The {@code --add-modules} flag
+ * resolves the module (making classes accessible), while this property tells
+ * the {@link java.net.URL} class which packages to search for the handler.
+ */
+ public static void ensureSafkeyringHandler() {
+ String[] packagePrefixes = {
+ "com.ibm.crypto.zsecurity.provider",
+ "com.ibm.crypto.hdwrCCA.provider"
+ };
+ String existing = System.getProperty("java.protocol.handler.pkgs", "");
+ StringBuilder sb = new StringBuilder(existing);
+ for (String prefix : packagePrefixes) {
+ if (!existing.contains(prefix)) {
+ if (!sb.isEmpty()) {
+ sb.append('|');
+ }
+ sb.append(prefix);
+ }
+ }
+ System.setProperty("java.protocol.handler.pkgs", sb.toString());
+ }
+}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/StoresNotInitializeException.java b/certificate-common/src/main/java/org/zowe/apiml/common/StoresNotInitializeException.java
similarity index 95%
rename from zosmf-jwt-check/src/main/java/org/zowe/apiml/StoresNotInitializeException.java
rename to certificate-common/src/main/java/org/zowe/apiml/common/StoresNotInitializeException.java
index c6b5231cf2..28fbba09b3 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/StoresNotInitializeException.java
+++ b/certificate-common/src/main/java/org/zowe/apiml/common/StoresNotInitializeException.java
@@ -8,7 +8,7 @@
* Copyright Contributors to the Zowe Project.
*/
-package org.zowe.apiml;
+package org.zowe.apiml.common;
/**
* Thrown when keystore or truststore initialization fails
diff --git a/certificate-common/src/test/java/org/zowe/apiml/common/KeyringUtilsTest.java b/certificate-common/src/test/java/org/zowe/apiml/common/KeyringUtilsTest.java
new file mode 100644
index 0000000000..6fb3eff9ec
--- /dev/null
+++ b/certificate-common/src/test/java/org/zowe/apiml/common/KeyringUtilsTest.java
@@ -0,0 +1,224 @@
+/*
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Copyright Contributors to the Zowe Project.
+ */
+
+package org.zowe.apiml.common;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+class KeyringUtilsTest {
+
+ @Nested
+ class IsKeyring {
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "safkeyring://userId/keyRing",
+ "safkeyring:////userId/keyRing",
+ "safkeyringjce://userId/keyRing",
+ "safkeyringjcecca://userId/keyRing",
+ "safkeyringjcehybrid://user1/ring2"
+ })
+ void givenValidKeyringUri_thenReturnsTrue(String input) {
+ assertTrue(KeyringUtils.isKeyring(input));
+ }
+
+ @ParameterizedTest
+ @NullAndEmptySource
+ @ValueSource(strings = {
+ "/path/to/keystore.p12",
+ "file:///some/path",
+ "safkeyring:/missing-slash/ring",
+ "safkeyring://",
+ "safkeyring://userOnly"
+ })
+ void givenNonKeyringInput_thenReturnsFalse(String input) {
+ assertFalse(KeyringUtils.isKeyring(input));
+ }
+ }
+
+ @Nested
+ class FormatKeyringUrl {
+
+ @ParameterizedTest
+ @CsvSource({
+ "safkeyring://userId/keyRing, safkeyring://userId/keyRing",
+ "safkeyring:////userId/keyRing, safkeyring://userId/keyRing",
+ "safkeyringjce://user/ring, safkeyringjce://user/ring",
+ "safkeyringjcecca:///user1/ring2, safkeyringjcecca://user1/ring2"
+ })
+ void givenKeyringUri_thenNormalizesToTwoSlashes(String input, String expected) {
+ assertEquals(expected, KeyringUtils.formatKeyringUrl(input));
+ }
+
+ @Test
+ void givenNonKeyringPath_thenReturnsUnchanged() {
+ String path = "/path/to/keystore.p12";
+ assertEquals(path, KeyringUtils.formatKeyringUrl(path));
+ }
+
+ @Test
+ void givenNull_thenReturnsNull() {
+ assertNull(KeyringUtils.formatKeyringUrl(null));
+ }
+ }
+
+ @Nested
+ class KeyRingUrl {
+
+ @Test
+ void givenInvalidKeyringFormat_thenThrowsStoresNotInitializeException() {
+ assertThrows(StoresNotInitializeException.class,
+ () -> KeyringUtils.keyRingUrl("/some/file/path"));
+ }
+
+ @Test
+ void givenInvalidFormat_thenExceptionContainsHelpfulMessage() {
+ StoresNotInitializeException ex = assertThrows(StoresNotInitializeException.class,
+ () -> KeyringUtils.keyRingUrl("notakeyring"));
+ assertThat(ex.getMessage(), containsString("Incorrect key ring format"));
+ assertThat(ex.getMessage(), containsString("safkeyring://userId/keyRing"));
+ }
+
+ @Test
+ void givenNullInput_thenThrowsStoresNotInitializeException() {
+ assertThrows(StoresNotInitializeException.class,
+ () -> KeyringUtils.keyRingUrl(null));
+ }
+ }
+
+ @Nested
+ class ReadKeyStore {
+
+ @Test
+ void givenValidPKCS12Stream_thenLoadsKeyStore() throws Exception {
+ // Use the test keystore from the project
+ String truststorePath = "../keystore/localhost/localhost.truststore.p12";
+ java.io.File file = new java.io.File(truststorePath);
+ if (!file.exists()) {
+ // Skip if keystore not available in this environment
+ return;
+ }
+
+ try (InputStream is = new java.io.FileInputStream(file)) {
+ KeyStore ks = KeyringUtils.readKeyStore(is, "password".toCharArray(), "PKCS12");
+ assertNotNull(ks);
+ assertTrue(ks.size() > 0);
+ }
+ }
+
+ @Test
+ void givenInvalidPassword_thenThrowsIOException() {
+ String truststorePath = "../keystore/localhost/localhost.truststore.p12";
+ java.io.File file = new java.io.File(truststorePath);
+ if (!file.exists()) {
+ return;
+ }
+
+ assertThrows(IOException.class, () -> {
+ try (InputStream is = new java.io.FileInputStream(file)) {
+ KeyringUtils.readKeyStore(is, "wrongpassword".toCharArray(), "PKCS12");
+ }
+ });
+ }
+
+ @Test
+ void givenInvalidType_thenThrowsKeyStoreException() {
+ InputStream is = new ByteArrayInputStream(new byte[0]);
+ assertThrows(KeyStoreException.class,
+ () -> KeyringUtils.readKeyStore(is, "pass".toCharArray(), "INVALID_TYPE"));
+ }
+ }
+
+ @Nested
+ class EnsureSafkeyringHandler {
+
+ @Test
+ void givenEmptyProperty_thenSetsHandlerPackages() {
+ String original = System.getProperty("java.protocol.handler.pkgs", "");
+ try {
+ System.setProperty("java.protocol.handler.pkgs", "");
+
+ KeyringUtils.ensureSafkeyringHandler();
+
+ String result = System.getProperty("java.protocol.handler.pkgs");
+ assertThat(result, containsString("com.ibm.crypto.zsecurity.provider"));
+ assertThat(result, containsString("com.ibm.crypto.hdwrCCA.provider"));
+ } finally {
+ System.setProperty("java.protocol.handler.pkgs", original);
+ }
+ }
+
+ @Test
+ void givenExistingPackages_thenAppendsWithPipeSeparator() {
+ String original = System.getProperty("java.protocol.handler.pkgs", "");
+ try {
+ System.setProperty("java.protocol.handler.pkgs", "com.example.handler");
+
+ KeyringUtils.ensureSafkeyringHandler();
+
+ String result = System.getProperty("java.protocol.handler.pkgs");
+ assertThat(result, startsWith("com.example.handler|"));
+ assertThat(result, containsString("com.ibm.crypto.zsecurity.provider"));
+ assertThat(result, containsString("com.ibm.crypto.hdwrCCA.provider"));
+ } finally {
+ System.setProperty("java.protocol.handler.pkgs", original);
+ }
+ }
+
+ @Test
+ void givenAlreadyRegistered_thenDoesNotDuplicate() {
+ String original = System.getProperty("java.protocol.handler.pkgs", "");
+ try {
+ System.setProperty("java.protocol.handler.pkgs", "");
+
+ KeyringUtils.ensureSafkeyringHandler();
+ String firstRun = System.getProperty("java.protocol.handler.pkgs");
+
+ KeyringUtils.ensureSafkeyringHandler();
+ String secondRun = System.getProperty("java.protocol.handler.pkgs");
+
+ assertEquals(firstRun, secondRun);
+ } finally {
+ System.setProperty("java.protocol.handler.pkgs", original);
+ }
+ }
+ }
+
+ @Nested
+ class StoresNotInitializeExceptionTest {
+
+ @Test
+ void givenMessage_thenExceptionContainsIt() {
+ StoresNotInitializeException ex = new StoresNotInitializeException("test error");
+ assertEquals("test error", ex.getMessage());
+ }
+
+ @Test
+ void givenException_thenIsRuntimeException() {
+ StoresNotInitializeException ex = new StoresNotInitializeException("msg");
+ assertThat(ex, instanceOf(RuntimeException.class));
+ }
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index aeb34a82e7..290b442fd1 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -54,6 +54,7 @@ include 'onboarding-enabler-python'
include 'zaas-client'
include 'mock-services'
include 'certificate-analyser'
+include 'certificate-common'
include 'zosmf-jwt-check'
include 'apiml-tomcat-common'
include 'apiml-sample-extension'
diff --git a/zosmf-jwt-check/build.gradle b/zosmf-jwt-check/build.gradle
index 8a6d9a333d..f65a9adf8a 100644
--- a/zosmf-jwt-check/build.gradle
+++ b/zosmf-jwt-check/build.gradle
@@ -3,6 +3,7 @@ plugins {
}
dependencies {
+ implementation project(':certificate-common')
implementation libs.picocli
annotationProcessor libs.picocli.codegen
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
index e4bbcf3963..c48aa1a3af 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfJwtCheck.java
@@ -30,7 +30,7 @@ public class ZosmfJwtCheck {
static final String VERIFY_DISABLED = "DISABLED";
public static int mainWithExitCode(String[] args) {
- ensureSafkeyringHandler();
+ org.zowe.apiml.common.KeyringUtils.ensureSafkeyringHandler();
try {
ZosmfJwtCheckConf conf = new ZosmfJwtCheckConf();
@@ -101,34 +101,6 @@ static void validateConfig(ZosmfJwtCheckConf conf) {
}
}
- /**
- * Registers IBM SAF keyring URL protocol handler packages via the
- * {@code java.protocol.handler.pkgs} system property.
- *
- * On IBM Java 17/21 (z/OS), this property works in conjunction with
- * {@code --add-modules ibm.crypto.zsecurity,ibm.crypto.hdwrcca} to enable
- * the {@code safkeyring://} URL protocol. The {@code --add-modules} flag
- * resolves the module (making classes accessible), while this property tells
- * the {@link java.net.URL} class which packages to search for the handler.
- */
- static void ensureSafkeyringHandler() {
- String[] packagePrefixes = {
- "com.ibm.crypto.zsecurity.provider",
- "com.ibm.crypto.hdwrCCA.provider"
- };
- String existing = System.getProperty("java.protocol.handler.pkgs", "");
- StringBuilder sb = new StringBuilder(existing);
- for (String prefix : packagePrefixes) {
- if (!existing.contains(prefix)) {
- if (!sb.isEmpty()) {
- sb.append('|');
- }
- sb.append(prefix);
- }
- }
- System.setProperty("java.protocol.handler.pkgs", sb.toString());
- }
-
public static void main(String[] args) {
System.exit(mainWithExitCode(args));
}
diff --git a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfStores.java b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfStores.java
index d393a5d395..00b8e88f2f 100644
--- a/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfStores.java
+++ b/zosmf-jwt-check/src/main/java/org/zowe/apiml/ZosmfStores.java
@@ -10,18 +10,18 @@
package org.zowe.apiml;
+import org.zowe.apiml.common.KeyringUtils;
+import org.zowe.apiml.common.StoresNotInitializeException;
+
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
-import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* Loads Java {@link java.security.KeyStore} instances from the filesystem
@@ -30,8 +30,6 @@
@SuppressWarnings("squid:S106")
public class ZosmfStores {
- private static final Pattern KEYRING_PATTERN = Pattern.compile("^(safkeyring[^:]*):/{2,4}([^/]+)/([^/]+)$");
-
private KeyStore keyStore;
private KeyStore trustStore;
private final ZosmfJwtCheckConfig conf;
@@ -41,33 +39,6 @@ public ZosmfStores(ZosmfJwtCheckConfig conf) {
init();
}
- /**
- * Checks whether the given path is a SAF keyring URI.
- *
- * @param input store path to check
- * @return {@code true} if the path matches the keyring pattern
- */
- public static boolean isKeyring(String input) {
- if (input == null) return false;
- Matcher matcher = KEYRING_PATTERN.matcher(input);
- return matcher.matches();
- }
-
- /**
- * Normalizes a keyring URI to the canonical {@code safkeyring://userId/keyRing} format.
- *
- * @param input raw keyring URI
- * @return normalized URI, or the original input if not a keyring
- */
- public static String formatKeyringUrl(String input) {
- if (input == null) return null;
- Matcher matcher = KEYRING_PATTERN.matcher(input);
- if (matcher.matches()) {
- return matcher.group(1) + "://" + matcher.group(2) + "/" + matcher.group(3);
- }
- return input;
- }
-
void init() {
try {
initKeystore();
@@ -92,14 +63,14 @@ private void initTruststore() throws IOException, CertificateException, NoSuchAl
}
return;
}
- if (isKeyring(conf.getTrustStore())) {
- URL url = keyRingUrl(conf.getTrustStore());
+ if (KeyringUtils.isKeyring(conf.getTrustStore())) {
+ URL url = KeyringUtils.keyRingUrl(conf.getTrustStore());
try (InputStream trustStoreIStream = url.openStream()) {
- this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
+ this.trustStore = KeyringUtils.readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
}
} else {
try (InputStream trustStoreIStream = new FileInputStream(conf.getTrustStore())) {
- this.trustStore = readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
+ this.trustStore = KeyringUtils.readKeyStore(trustStoreIStream, conf.getTrustStorePassword().toCharArray(), conf.getTrustStoreType());
}
}
}
@@ -108,26 +79,20 @@ private void initKeystore() throws IOException, CertificateException, NoSuchAlgo
if (conf.getKeyStore() == null) {
return;
}
- if (isKeyring(conf.getKeyStore())) {
- try (InputStream keyringIStream = keyRingUrl(conf.getKeyStore()).openStream()) {
- this.keyStore = readKeyStore(keyringIStream, conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
+ if (KeyringUtils.isKeyring(conf.getKeyStore())) {
+ try (InputStream keyringIStream = KeyringUtils.keyRingUrl(conf.getKeyStore()).openStream()) {
+ this.keyStore = KeyringUtils.readKeyStore(keyringIStream, conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
this.trustStore = this.keyStore;
} catch (Exception e) {
throw new StoresNotInitializeException(e.getMessage());
}
} else {
try (InputStream keyStoreIStream = new FileInputStream(conf.getKeyStore())) {
- this.keyStore = readKeyStore(keyStoreIStream, conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
+ this.keyStore = KeyringUtils.readKeyStore(keyStoreIStream, conf.getKeyStorePassword().toCharArray(), conf.getKeyStoreType());
}
}
}
- public static KeyStore readKeyStore(InputStream is, char[] pass, String type) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
- KeyStore keyStore = KeyStore.getInstance(type);
- keyStore.load(is, pass);
- return keyStore;
- }
-
public KeyStore getKeyStore() {
return keyStore;
}
@@ -139,20 +104,4 @@ public KeyStore getTrustStore() {
public ZosmfJwtCheckConfig getConf() {
return conf;
}
-
- public static URL keyRingUrl(String uri) throws MalformedURLException {
- if (!isKeyring(uri)) {
- throw new StoresNotInitializeException("Incorrect key ring format: " + uri
- + ". Make sure you use format safkeyring://userId/keyRing");
- }
- String formatted = formatKeyringUrl(uri);
- try {
- return new URL(formatted);
- } catch (MalformedURLException e) {
- System.err.println("ERROR: Unknown protocol in '" + formatted + "': " + e.getMessage());
- System.err.println("Ensure the JVM is started with: --add-modules ibm.crypto.zsecurity,ibm.crypto.hdwrcca");
- System.err.println("And that ensureSafkeyringHandler() has been called to set java.protocol.handler.pkgs");
- throw e;
- }
- }
}
diff --git a/zosmf-jwt-check/src/test/java/org/zowe/apiml/HttpClientWrapperTest.java b/zosmf-jwt-check/src/test/java/org/zowe/apiml/HttpClientWrapperTest.java
new file mode 100644
index 0000000000..81a8ec2f09
--- /dev/null
+++ b/zosmf-jwt-check/src/test/java/org/zowe/apiml/HttpClientWrapperTest.java
@@ -0,0 +1,162 @@
+/*
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Copyright Contributors to the Zowe Project.
+ */
+
+package org.zowe.apiml;
+
+import com.sun.net.httpserver.HttpServer;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class HttpClientWrapperTest {
+
+ @Nested
+ class HttpMode {
+
+ private HttpServer server;
+ private int port;
+
+ @BeforeEach
+ void startServer() throws IOException {
+ server = HttpServer.create(new InetSocketAddress(0), 0);
+ port = server.getAddress().getPort();
+ }
+
+ @AfterEach
+ void stopServer() {
+ if (server != null) {
+ server.stop(0);
+ }
+ }
+
+ @Test
+ void executesGetRequestAndReturnsResponse() throws IOException {
+ server.createContext("/test", exchange -> {
+ String body = "{\"status\":\"ok\"}";
+ exchange.sendResponseHeaders(200, body.length());
+ try (OutputStream os = exchange.getResponseBody()) {
+ os.write(body.getBytes());
+ }
+ });
+ server.start();
+
+ HttpClientWrapper client = new HttpClientWrapper();
+ URL url = new URL("http://localhost:" + port + "/test");
+
+ HttpClientWrapper.Response response = client.executeCall(url, new HashMap<>());
+
+ assertEquals(200, response.getStatusCode());
+ assertEquals("{\"status\":\"ok\"}", response.getBody());
+ }
+
+ @Test
+ void passesHeadersToServer() throws IOException {
+ final String[] receivedHeader = {null};
+ server.createContext("/headers", exchange -> {
+ receivedHeader[0] = exchange.getRequestHeaders().getFirst("X-Custom-Header");
+ exchange.sendResponseHeaders(204, -1);
+ });
+ server.start();
+
+ HttpClientWrapper client = new HttpClientWrapper();
+ URL url = new URL("http://localhost:" + port + "/headers");
+ Map headers = new HashMap<>();
+ headers.put("X-Custom-Header", "test-value");
+
+ client.executeCall(url, headers);
+
+ assertEquals("test-value", receivedHeader[0]);
+ }
+
+ @Test
+ void handles404Response() throws IOException {
+ server.createContext("/notfound", exchange -> {
+ String body = "Not Found";
+ exchange.sendResponseHeaders(404, body.length());
+ try (OutputStream os = exchange.getResponseBody()) {
+ os.write(body.getBytes());
+ }
+ });
+ server.start();
+
+ HttpClientWrapper client = new HttpClientWrapper();
+ URL url = new URL("http://localhost:" + port + "/notfound");
+
+ HttpClientWrapper.Response response = client.executeCall(url, new HashMap<>());
+
+ assertEquals(404, response.getStatusCode());
+ assertTrue(response.getBody().contains("Not Found"));
+ }
+
+ @Test
+ void handles500Response() throws IOException {
+ server.createContext("/error", exchange -> {
+ String body = "Internal Server Error";
+ exchange.sendResponseHeaders(500, body.length());
+ try (OutputStream os = exchange.getResponseBody()) {
+ os.write(body.getBytes());
+ }
+ });
+ server.start();
+
+ HttpClientWrapper client = new HttpClientWrapper();
+ URL url = new URL("http://localhost:" + port + "/error");
+
+ HttpClientWrapper.Response response = client.executeCall(url, new HashMap<>());
+
+ assertEquals(500, response.getStatusCode());
+ }
+
+ @Test
+ void handlesNullHeaders() throws IOException {
+ server.createContext("/nullheaders", exchange -> {
+ exchange.sendResponseHeaders(200, -1);
+ });
+ server.start();
+
+ HttpClientWrapper client = new HttpClientWrapper();
+ URL url = new URL("http://localhost:" + port + "/nullheaders");
+
+ HttpClientWrapper.Response response = client.executeCall(url, null);
+
+ assertEquals(200, response.getStatusCode());
+ }
+ }
+
+ @Nested
+ class ResponseClass {
+
+ @Test
+ void responseHoldsStatusCodeAndBody() {
+ HttpClientWrapper.Response response = new HttpClientWrapper.Response(201, "created");
+
+ assertEquals(201, response.getStatusCode());
+ assertEquals("created", response.getBody());
+ }
+
+ @Test
+ void responseCanHaveNullBody() {
+ HttpClientWrapper.Response response = new HttpClientWrapper.Response(204, null);
+
+ assertEquals(204, response.getStatusCode());
+ assertNull(response.getBody());
+ }
+ }
+}
diff --git a/zosmf-jwt-check/src/test/java/org/zowe/apiml/SSLContextFactoryTest.java b/zosmf-jwt-check/src/test/java/org/zowe/apiml/SSLContextFactoryTest.java
new file mode 100644
index 0000000000..fb96efe76f
--- /dev/null
+++ b/zosmf-jwt-check/src/test/java/org/zowe/apiml/SSLContextFactoryTest.java
@@ -0,0 +1,112 @@
+/*
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Copyright Contributors to the Zowe Project.
+ */
+
+package org.zowe.apiml;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import javax.net.ssl.SSLContext;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class SSLContextFactoryTest {
+
+ private static final String TRUSTSTORE_PATH = "../keystore/localhost/localhost.truststore.p12";
+ private static final String KEYSTORE_PATH = "../keystore/localhost/localhost.keystore.p12";
+ private static final String PASSWORD = "password";
+
+ @Nested
+ class InitSSLContextWithRealStores {
+
+ @Test
+ void createsNonNullSSLContext() throws Exception {
+ ZosmfJwtCheckConfig conf = createConf(null, null, TRUSTSTORE_PATH, PASSWORD, "PKCS12");
+ ZosmfStores stores = new ZosmfStores(conf);
+
+ SSLContextFactory factory = SSLContextFactory.initSSLContext(stores);
+
+ assertNotNull(factory);
+ assertNotNull(factory.getSslContext());
+ }
+
+ @Test
+ void sslContextProtocolIsTLS() throws Exception {
+ ZosmfJwtCheckConfig conf = createConf(null, null, TRUSTSTORE_PATH, PASSWORD, "PKCS12");
+ ZosmfStores stores = new ZosmfStores(conf);
+
+ SSLContextFactory factory = SSLContextFactory.initSSLContext(stores);
+
+ assertEquals("TLSv1.2", factory.getSslContext().getProtocol());
+ }
+
+ @Test
+ void worksWithKeystoreAndTruststore() throws Exception {
+ ZosmfJwtCheckConfig conf = createConf(KEYSTORE_PATH, PASSWORD, TRUSTSTORE_PATH, PASSWORD, "PKCS12");
+ ZosmfStores stores = new ZosmfStores(conf);
+
+ SSLContextFactory factory = SSLContextFactory.initSSLContext(stores);
+
+ assertNotNull(factory.getSslContext());
+ }
+
+ @Test
+ void worksWithoutKeystore() throws Exception {
+ ZosmfJwtCheckConfig conf = createConf(null, null, TRUSTSTORE_PATH, PASSWORD, "PKCS12");
+ ZosmfStores stores = new ZosmfStores(conf);
+
+ SSLContextFactory factory = SSLContextFactory.initSSLContext(stores);
+
+ SSLContext ctx = factory.getSslContext();
+ assertNotNull(ctx.getSocketFactory());
+ }
+ }
+
+ @Nested
+ class InitTrustAllSSLContext {
+
+ @Test
+ void createsNonNullSSLContext() throws Exception {
+ SSLContextFactory factory = SSLContextFactory.initTrustAllSSLContext();
+
+ assertNotNull(factory);
+ assertNotNull(factory.getSslContext());
+ }
+
+ @Test
+ void sslContextProtocolIsTLS() throws Exception {
+ SSLContextFactory factory = SSLContextFactory.initTrustAllSSLContext();
+
+ assertEquals("TLSv1.2", factory.getSslContext().getProtocol());
+ }
+
+ @Test
+ void socketFactoryIsAvailable() throws Exception {
+ SSLContextFactory factory = SSLContextFactory.initTrustAllSSLContext();
+
+ assertNotNull(factory.getSslContext().getSocketFactory());
+ }
+ }
+
+ private ZosmfJwtCheckConfig createConf(String keyStore, String keyStorePassword,
+ String trustStore, String trustStorePassword,
+ String storeType) {
+ ZosmfJwtCheckConfig conf = mock(ZosmfJwtCheckConfig.class);
+ when(conf.getKeyStore()).thenReturn(keyStore);
+ when(conf.getKeyStorePassword()).thenReturn(keyStorePassword);
+ when(conf.getKeyStoreType()).thenReturn(storeType);
+ when(conf.getTrustStore()).thenReturn(trustStore);
+ when(conf.getTrustStorePassword()).thenReturn(trustStorePassword);
+ when(conf.getTrustStoreType()).thenReturn(storeType);
+ return conf;
+ }
+}
diff --git a/zosmf-jwt-check/src/test/java/org/zowe/apiml/ZosmfStoresTest.java b/zosmf-jwt-check/src/test/java/org/zowe/apiml/ZosmfStoresTest.java
new file mode 100644
index 0000000000..f97e36b48f
--- /dev/null
+++ b/zosmf-jwt-check/src/test/java/org/zowe/apiml/ZosmfStoresTest.java
@@ -0,0 +1,183 @@
+/*
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Copyright Contributors to the Zowe Project.
+ */
+
+package org.zowe.apiml;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.zowe.apiml.common.KeyringUtils;
+import org.zowe.apiml.common.StoresNotInitializeException;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.security.KeyStore;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class ZosmfStoresTest {
+
+ private static final String TRUSTSTORE_PATH = "../keystore/localhost/localhost.truststore.p12";
+ private static final String KEYSTORE_PATH = "../keystore/localhost/localhost.keystore.p12";
+ private static final String PASSWORD = "password";
+
+ @Nested
+ class GivenValidFileBasedTruststore {
+
+ @Test
+ void storesAreInitializedSuccessfully() {
+ ZosmfJwtCheckConfig conf = createConf(null, null, TRUSTSTORE_PATH, PASSWORD, "PKCS12");
+ ZosmfStores stores = new ZosmfStores(conf);
+
+ assertNotNull(stores.getTrustStore());
+ }
+
+ @Test
+ void truststoreContainsCertificates() throws Exception {
+ ZosmfJwtCheckConfig conf = createConf(null, null, TRUSTSTORE_PATH, PASSWORD, "PKCS12");
+ ZosmfStores stores = new ZosmfStores(conf);
+
+ assertTrue(stores.getTrustStore().size() > 0);
+ }
+ }
+
+ @Nested
+ class GivenValidFileBasedKeystore {
+
+ @Test
+ void keystoreAndTruststoreAreLoaded() {
+ ZosmfJwtCheckConfig conf = createConf(KEYSTORE_PATH, PASSWORD, TRUSTSTORE_PATH, PASSWORD, "PKCS12");
+ ZosmfStores stores = new ZosmfStores(conf);
+
+ assertNotNull(stores.getKeyStore());
+ assertNotNull(stores.getTrustStore());
+ }
+
+ @Test
+ void confIsAccessible() {
+ ZosmfJwtCheckConfig conf = createConf(null, null, TRUSTSTORE_PATH, PASSWORD, "PKCS12");
+ ZosmfStores stores = new ZosmfStores(conf);
+
+ assertSame(conf, stores.getConf());
+ }
+ }
+
+ @Nested
+ class GivenNoTruststore {
+
+ @Test
+ void emptyTruststoreIsCreated() {
+ ZosmfJwtCheckConfig conf = createConf(null, null, null, null, "PKCS12");
+ ZosmfStores stores = new ZosmfStores(conf);
+
+ assertNotNull(stores.getTrustStore());
+ }
+ }
+
+ @Nested
+ class GivenInvalidPaths {
+
+ @Test
+ void nonExistentTruststoreThrowsException() {
+ ZosmfJwtCheckConfig conf = createConf(null, null, "/nonexistent/path.p12", PASSWORD, "PKCS12");
+
+ StoresNotInitializeException e = assertThrows(StoresNotInitializeException.class,
+ () -> new ZosmfStores(conf));
+ assertNotNull(e.getMessage());
+ }
+
+ @Test
+ void wrongPasswordThrowsException() {
+ ZosmfJwtCheckConfig conf = createConf(null, null, TRUSTSTORE_PATH, "wrongpassword", "PKCS12");
+
+ assertThrows(StoresNotInitializeException.class, () -> new ZosmfStores(conf));
+ }
+
+ @Test
+ void nonExistentKeystoreThrowsException() {
+ ZosmfJwtCheckConfig conf = createConf("/nonexistent/key.p12", PASSWORD, TRUSTSTORE_PATH, PASSWORD, "PKCS12");
+
+ StoresNotInitializeException e = assertThrows(StoresNotInitializeException.class,
+ () -> new ZosmfStores(conf));
+ assertTrue(e.getMessage().contains("Error while loading keystore file"));
+ }
+ }
+
+ @Nested
+ class KeyringUtilsDelegation {
+
+ @Test
+ void isKeyringReturnsTrueForValidUri() {
+ assertTrue(KeyringUtils.isKeyring("safkeyring://userId/keyRing"));
+ assertTrue(KeyringUtils.isKeyring("safkeyring:////userId/keyRing"));
+ }
+
+ @Test
+ void isKeyringReturnsFalseForNonKeyring() {
+ assertFalse(KeyringUtils.isKeyring(null));
+ assertFalse(KeyringUtils.isKeyring("/path/to/file.p12"));
+ assertFalse(KeyringUtils.isKeyring("https://server/resource"));
+ }
+
+ @Test
+ void formatKeyringUrlNormalizesSlashes() {
+ assertEquals("safkeyring://userId/keyRing",
+ KeyringUtils.formatKeyringUrl("safkeyring:////userId/keyRing"));
+ }
+
+ @Test
+ void formatKeyringUrlReturnsInputForNonKeyring() {
+ assertEquals("/some/path.p12", KeyringUtils.formatKeyringUrl("/some/path.p12"));
+ assertNull(KeyringUtils.formatKeyringUrl(null));
+ }
+
+ @Test
+ void keyRingUrlThrowsForInvalidFormat() {
+ assertThrows(StoresNotInitializeException.class,
+ () -> KeyringUtils.keyRingUrl("not://a/keyring/format/extra"));
+ }
+
+ @Test
+ void keyRingUrlThrowsMalformedURLExceptionForValidButUnresolvable() {
+ // Valid keyring format but no protocol handler registered
+ assertThrows(MalformedURLException.class,
+ () -> KeyringUtils.keyRingUrl("safkeyring://userId/keyRing"));
+ }
+
+ @Test
+ void readKeyStoreLoadsFromStream() throws Exception {
+ // Create an in-memory PKCS12 keystore
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ ks.load(null, "test".toCharArray());
+
+ java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
+ ks.store(baos, "test".toCharArray());
+
+ InputStream is = new ByteArrayInputStream(baos.toByteArray());
+ KeyStore loaded = KeyringUtils.readKeyStore(is, "test".toCharArray(), "PKCS12");
+ assertNotNull(loaded);
+ }
+ }
+
+ private ZosmfJwtCheckConfig createConf(String keyStore, String keyStorePassword,
+ String trustStore, String trustStorePassword,
+ String storeType) {
+ ZosmfJwtCheckConfig conf = mock(ZosmfJwtCheckConfig.class);
+ when(conf.getKeyStore()).thenReturn(keyStore);
+ when(conf.getKeyStorePassword()).thenReturn(keyStorePassword);
+ when(conf.getKeyStoreType()).thenReturn(storeType);
+ when(conf.getTrustStore()).thenReturn(trustStore);
+ when(conf.getTrustStorePassword()).thenReturn(trustStorePassword);
+ when(conf.getTrustStoreType()).thenReturn(storeType);
+ return conf;
+ }
+}
From 6dd5a6ba338901138976fac90f46fcf63a949f27 Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Wed, 3 Jun 2026 16:36:41 +0530
Subject: [PATCH 26/28] test update
Signed-off-by: hrishikesh-nalawade
---
.../java/org/zowe/apiml/common/KeyringUtilsTest.java | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/certificate-common/src/test/java/org/zowe/apiml/common/KeyringUtilsTest.java b/certificate-common/src/test/java/org/zowe/apiml/common/KeyringUtilsTest.java
index 6fb3eff9ec..e988015886 100644
--- a/certificate-common/src/test/java/org/zowe/apiml/common/KeyringUtilsTest.java
+++ b/certificate-common/src/test/java/org/zowe/apiml/common/KeyringUtilsTest.java
@@ -129,18 +129,17 @@ void givenValidPKCS12Stream_thenLoadsKeyStore() throws Exception {
}
@Test
- void givenInvalidPassword_thenThrowsIOException() {
+ void givenInvalidPassword_thenThrowsIOException() throws Exception {
String truststorePath = "../keystore/localhost/localhost.truststore.p12";
java.io.File file = new java.io.File(truststorePath);
if (!file.exists()) {
return;
}
- assertThrows(IOException.class, () -> {
- try (InputStream is = new java.io.FileInputStream(file)) {
- KeyringUtils.readKeyStore(is, "wrongpassword".toCharArray(), "PKCS12");
- }
- });
+ try (InputStream is = new java.io.FileInputStream(file)) {
+ assertThrows(IOException.class,
+ () -> KeyringUtils.readKeyStore(is, "wrongpassword".toCharArray(), "PKCS12"));
+ }
}
@Test
From 92bc33c9c2a3befef61cfc5beaac6147f21d3314 Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Wed, 3 Jun 2026 17:17:22 +0530
Subject: [PATCH 27/28] increasing test coverage
Signed-off-by: hrishikesh-nalawade
---
.../test/java/org/zowe/apiml/StoresTest.java | 121 ++++++++++++++++++
.../zowe/apiml/common/KeyringUtilsTest.java | 8 ++
gradle/coverage.gradle | 2 +
.../org/zowe/apiml/HttpClientWrapperTest.java | 19 +++
.../zowe/apiml/JwkEndpointCheckerTest.java | 63 +++++++++
.../org/zowe/apiml/SSLContextFactoryTest.java | 44 +++++++
.../org/zowe/apiml/ZosmfJwtCheckTest.java | 69 ++++++++++
7 files changed, 326 insertions(+)
diff --git a/certificate-analyser/src/test/java/org/zowe/apiml/StoresTest.java b/certificate-analyser/src/test/java/org/zowe/apiml/StoresTest.java
index da5abc3bea..dcc7f58ab5 100644
--- a/certificate-analyser/src/test/java/org/zowe/apiml/StoresTest.java
+++ b/certificate-analyser/src/test/java/org/zowe/apiml/StoresTest.java
@@ -17,6 +17,9 @@
import picocli.CommandLine;
import java.net.MalformedURLException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
@@ -134,4 +137,122 @@ void givenFormatKeyringUrl_whenNull_thenReturnsNull() {
}
}
+ @Nested
+ class GivenValidStores {
+
+ private Stores createValidStores() {
+ String[] args = {"--keystore", "../keystore/localhost/localhost.keystore.p12",
+ "--truststore", "../keystore/localhost/localhost.truststore.p12",
+ "--keypasswd", "password",
+ "--keyalias", "localhost"};
+ ApimlConf conf = new ApimlConf();
+ new CommandLine(conf).parseArgs(args);
+ return new Stores(conf);
+ }
+
+ @Test
+ void getListOfCertificatesReturnsNonEmptyMap() throws Exception {
+ Stores stores = createValidStores();
+ Map certs = stores.getListOfCertificates();
+ assertNotNull(certs);
+ assertFalse(certs.isEmpty());
+ }
+
+ @Test
+ void getListOfCertificatesReturnsCachedMap() throws Exception {
+ Stores stores = createValidStores();
+ Map first = stores.getListOfCertificates();
+ Map second = stores.getListOfCertificates();
+ assertSame(first, second);
+ }
+
+ @Test
+ void getX509CertificateReturnsValidCert() throws Exception {
+ Stores stores = createValidStores();
+ X509Certificate cert = stores.getX509Certificate("localhost");
+ assertNotNull(cert);
+ }
+
+ @Test
+ void getServerCertificateChainWithNullAliasUsesFirstAlias() throws Exception {
+ Stores stores = createValidStores();
+ Certificate[] chain = stores.getServerCertificateChain(null);
+ assertNotNull(chain);
+ assertTrue(chain.length > 0);
+ }
+
+ @Test
+ void getServerCertificateChainWithExplicitAlias() throws Exception {
+ Stores stores = createValidStores();
+ Certificate[] chain = stores.getServerCertificateChain("localhost");
+ assertNotNull(chain);
+ assertTrue(chain.length > 0);
+ }
+
+ @Test
+ void getKeyStoreReturnsNonNull() {
+ Stores stores = createValidStores();
+ assertNotNull(stores.getKeyStore());
+ }
+
+ @Test
+ void getTrustStoreReturnsNonNull() {
+ Stores stores = createValidStores();
+ assertNotNull(stores.getTrustStore());
+ }
+
+ @Test
+ void getConfReturnsNonNull() {
+ Stores stores = createValidStores();
+ assertNotNull(stores.getConf());
+ }
+ }
+
+ @Nested
+ class GivenNullKeystore {
+
+ @Test
+ void whenKeystoreIsNull_thenEmptyKeystoreCreated() {
+ String[] args = {"--truststore", "../keystore/localhost/localhost.truststore.p12",
+ "--keypasswd", "password"};
+ ApimlConf conf = new ApimlConf();
+ new CommandLine(conf).parseArgs(args);
+ Stores stores = new Stores(conf);
+ assertNotNull(stores.getTrustStore());
+ }
+ }
+
+ @Nested
+ class GivenNullTruststore {
+
+ @Test
+ void whenTruststoreIsNull_thenEmptyTruststoreCreated() {
+ String[] args = {"--keystore", "../keystore/localhost/localhost.keystore.p12",
+ "--keypasswd", "password",
+ "--keyalias", "localhost"};
+ ApimlConf conf = new ApimlConf();
+ new CommandLine(conf).parseArgs(args);
+ Stores stores = new Stores(conf);
+ assertNotNull(stores.getTrustStore());
+ }
+ }
+
+ @Nested
+ class GivenMissingAlias {
+
+ @Test
+ void getX509CertificateThrowsForNonexistentAlias() {
+ String[] args = {"--keystore", "../keystore/localhost/localhost.keystore.p12",
+ "--truststore", "../keystore/localhost/localhost.truststore.p12",
+ "--keypasswd", "password",
+ "--keyalias", "localhost"};
+ ApimlConf conf = new ApimlConf();
+ new CommandLine(conf).parseArgs(args);
+ Stores stores = new Stores(conf);
+ // getCertificateChain returns null for nonexistent alias, causing NPE
+ assertThrows(NullPointerException.class,
+ () -> stores.getX509Certificate("nonexistent-alias"));
+ }
+ }
+
}
diff --git a/certificate-common/src/test/java/org/zowe/apiml/common/KeyringUtilsTest.java b/certificate-common/src/test/java/org/zowe/apiml/common/KeyringUtilsTest.java
index e988015886..08d0abd772 100644
--- a/certificate-common/src/test/java/org/zowe/apiml/common/KeyringUtilsTest.java
+++ b/certificate-common/src/test/java/org/zowe/apiml/common/KeyringUtilsTest.java
@@ -106,6 +106,14 @@ void givenNullInput_thenThrowsStoresNotInitializeException() {
assertThrows(StoresNotInitializeException.class,
() -> KeyringUtils.keyRingUrl(null));
}
+
+ @Test
+ void givenValidKeyringFormat_thenThrowsMalformedURLExceptionWithoutHandler() {
+ // Valid keyring format but no SAF protocol handler registered on this JVM
+ java.net.MalformedURLException ex = assertThrows(java.net.MalformedURLException.class,
+ () -> KeyringUtils.keyRingUrl("safkeyring://userId/keyRing"));
+ assertThat(ex.getMessage(), containsString("unknown protocol"));
+ }
}
@Nested
diff --git a/gradle/coverage.gradle b/gradle/coverage.gradle
index 01e3f2175a..746d7ad7e2 100644
--- a/gradle/coverage.gradle
+++ b/gradle/coverage.gradle
@@ -20,6 +20,8 @@ ext.javaProjectsWithUnitTests = [
'apiml-security-common',
'zaas-client',
'certificate-analyser',
+ 'certificate-common',
+ 'zosmf-jwt-check',
'apiml-tomcat-common',
'apiml-extension-loader',
'zaas-service'
diff --git a/zosmf-jwt-check/src/test/java/org/zowe/apiml/HttpClientWrapperTest.java b/zosmf-jwt-check/src/test/java/org/zowe/apiml/HttpClientWrapperTest.java
index 81a8ec2f09..056af0c3c9 100644
--- a/zosmf-jwt-check/src/test/java/org/zowe/apiml/HttpClientWrapperTest.java
+++ b/zosmf-jwt-check/src/test/java/org/zowe/apiml/HttpClientWrapperTest.java
@@ -138,6 +138,25 @@ void handlesNullHeaders() throws IOException {
assertEquals(200, response.getStatusCode());
}
+
+ @Test
+ void readBodyReturnsNullWhenStreamThrows() throws IOException {
+ server.createContext("/broken", exchange -> {
+ // Send headers claiming 100 bytes, then close immediately without body
+ exchange.sendResponseHeaders(200, 100);
+ exchange.getResponseBody().close();
+ });
+ server.start();
+
+ HttpClientWrapper client = new HttpClientWrapper();
+ URL url = new URL("http://localhost:" + port + "/broken");
+
+ HttpClientWrapper.Response response = client.executeCall(url, null);
+
+ assertEquals(200, response.getStatusCode());
+ // readBody returns either truncated content or null if stream throws
+ // The body should be empty string or null depending on timing
+ }
}
@Nested
diff --git a/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java b/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
index 8e18228afb..0e2525d6a1 100644
--- a/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
+++ b/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
@@ -23,6 +23,7 @@
import java.io.PrintStream;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
@@ -142,5 +143,67 @@ void unexpectedExceptionIsHandled() throws IOException {
assertFalse(checker.check());
assertTrue(errStream.toString().contains("verify hostname and port"));
}
+
+ @Test
+ void unknownHostExceptionReportsResolutionFailure() throws IOException {
+ when(mockClient.executeCall(any(), anyMap())).thenThrow(new UnknownHostException("zosmf.example.com"));
+ JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
+ assertFalse(checker.check());
+ assertTrue(errStream.toString().contains("could not be resolved"));
+ }
+ }
+
+ @Nested
+ class VerboseMode {
+
+ @Test
+ void verbosePrintsResponseBody() throws IOException {
+ when(mockConf.isVerbose()).thenReturn(true);
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(
+ new HttpClientWrapper.Response(200, "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"n\":\"abc123\"}]}"));
+ JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
+ assertTrue(checker.check());
+ assertTrue(outStream.toString().contains("Response body:"));
+ }
+ }
+
+ @Nested
+ class ValidateJwkBody {
+
+ @Test
+ void nullBodyIsFailure() throws IOException {
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(200, null));
+ JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
+ assertFalse(checker.check());
+ assertTrue(errStream.toString().contains("empty response body"));
+ }
+
+ @Test
+ void emptyBodyIsFailure() throws IOException {
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(200, ""));
+ JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
+ assertFalse(checker.check());
+ assertTrue(errStream.toString().contains("empty response body"));
+ }
+
+ @Test
+ void bodyWithoutNKeyIsFailure() throws IOException {
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(200, "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\"}]}"));
+ JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
+ assertFalse(checker.check());
+ assertTrue(errStream.toString().contains("does not contain an RSA modulus"));
+ }
+ }
+
+ @Nested
+ class UnexpectedResponseCodes {
+
+ @Test
+ void unexpectedResponseCodeIsFailure() throws IOException {
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(301, ""));
+ JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
+ assertFalse(checker.check());
+ assertTrue(errStream.toString().contains("unexpected response code"));
+ }
}
}
diff --git a/zosmf-jwt-check/src/test/java/org/zowe/apiml/SSLContextFactoryTest.java b/zosmf-jwt-check/src/test/java/org/zowe/apiml/SSLContextFactoryTest.java
index fb96efe76f..9bfc2d42ef 100644
--- a/zosmf-jwt-check/src/test/java/org/zowe/apiml/SSLContextFactoryTest.java
+++ b/zosmf-jwt-check/src/test/java/org/zowe/apiml/SSLContextFactoryTest.java
@@ -10,10 +10,18 @@
package org.zowe.apiml;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.net.URL;
+import java.security.KeyStore;
+import java.util.Collections;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
@@ -95,6 +103,42 @@ void socketFactoryIsAvailable() throws Exception {
assertNotNull(factory.getSslContext().getSocketFactory());
}
+
+ @Test
+ void trustAllContextAcceptsSelfSignedCert() throws Exception {
+ // Start a local HTTPS server with a self-signed cert
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ try (InputStream is = new java.io.FileInputStream("../keystore/localhost/localhost.keystore.p12")) {
+ ks.load(is, "password".toCharArray());
+ }
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ kmf.init(ks, "password".toCharArray());
+ SSLContext serverCtx = SSLContext.getInstance("TLSv1.2");
+ serverCtx.init(kmf.getKeyManagers(), null, null);
+
+ HttpsServer server = HttpsServer.create(new InetSocketAddress(0), 0);
+ server.setHttpsConfigurator(new HttpsConfigurator(serverCtx));
+ server.createContext("/", exchange -> {
+ byte[] resp = "OK".getBytes();
+ exchange.sendResponseHeaders(200, resp.length);
+ exchange.getResponseBody().write(resp);
+ exchange.getResponseBody().close();
+ });
+ server.start();
+
+ try {
+ int port = server.getAddress().getPort();
+ SSLContextFactory factory = SSLContextFactory.initTrustAllSSLContext();
+ HttpClientWrapper client = new HttpClientWrapper(factory.getSslContext(), (hostname, session) -> true);
+ HttpClientWrapper.Response response = client.executeCall(
+ new URL("https://localhost:" + port + "/"), Collections.emptyMap());
+
+ assertEquals(200, response.getStatusCode());
+ assertEquals("OK", response.getBody());
+ } finally {
+ server.stop(0);
+ }
+ }
}
private ZosmfJwtCheckConfig createConf(String keyStore, String keyStorePassword,
diff --git a/zosmf-jwt-check/src/test/java/org/zowe/apiml/ZosmfJwtCheckTest.java b/zosmf-jwt-check/src/test/java/org/zowe/apiml/ZosmfJwtCheckTest.java
index 9da86cbcea..ea5499e2b3 100644
--- a/zosmf-jwt-check/src/test/java/org/zowe/apiml/ZosmfJwtCheckTest.java
+++ b/zosmf-jwt-check/src/test/java/org/zowe/apiml/ZosmfJwtCheckTest.java
@@ -115,4 +115,73 @@ void httpDoesNotRequireTruststore() {
assertFalse(errStream.toString().contains("--truststore-file is required"));
}
}
+
+ @Nested
+ class HttpsModes {
+
+ @Test
+ void httpsStrictWithValidTruststoreAttemptsConnection() {
+ String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "19999", "--scheme", "https",
+ "--truststore-file", "../keystore/localhost/localhost.truststore.p12",
+ "--truststore-password", "password",
+ "--verify-certificates", "STRICT"};
+ int exitCode = ZosmfJwtCheck.mainWithExitCode(args);
+ assertEquals(4, exitCode);
+ assertFalse(errStream.toString().contains("--truststore-file is required"));
+ }
+
+ @Test
+ void httpsNonstrictWithValidTruststoreAttemptsConnection() {
+ String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "19999", "--scheme", "https",
+ "--truststore-file", "../keystore/localhost/localhost.truststore.p12",
+ "--truststore-password", "password",
+ "--verify-certificates", "NONSTRICT"};
+ int exitCode = ZosmfJwtCheck.mainWithExitCode(args);
+ assertEquals(4, exitCode);
+ assertTrue(outStream.toString().contains("Hostname verification is disabled"));
+ }
+
+ @Test
+ void httpsStrictWithKeystoreAndTruststoreAttemptsConnection() {
+ String[] args = {"--zosmf-host", "localhost", "--zosmf-port", "19999", "--scheme", "https",
+ "--keystore-file", "../keystore/localhost/localhost.keystore.p12",
+ "--keystore-password", "password",
+ "--keystore-type", "PKCS12",
+ "--truststore-file", "../keystore/localhost/localhost.truststore.p12",
+ "--truststore-password", "password",
+ "--truststore-type", "PKCS12",
+ "--verify-certificates", "STRICT"};
+ int exitCode = ZosmfJwtCheck.mainWithExitCode(args);
+ assertEquals(4, exitCode);
+ }
+ }
+
+ @Nested
+ class ConfAccessors {
+
+ @Test
+ void allConfGettersReturnExpectedValues() {
+ ZosmfJwtCheckConf conf = new ZosmfJwtCheckConf();
+ picocli.CommandLine cmd = new picocli.CommandLine(conf);
+ cmd.parseArgs("--zosmf-host", "myhost", "--zosmf-port", "10443",
+ "--scheme", "https", "--keystore-file", "/ks.p12",
+ "--keystore-password", "kspass", "--keystore-type", "JKS",
+ "--truststore-file", "/ts.p12", "--truststore-password", "tspass",
+ "--truststore-type", "PKCS12", "--verify-certificates", "NONSTRICT",
+ "-v");
+
+ assertEquals("myhost", conf.getZosmfHost());
+ assertEquals(10443, conf.getZosmfPort());
+ assertEquals("https", conf.getScheme());
+ assertEquals("/ks.p12", conf.getKeyStore());
+ assertEquals("kspass", conf.getKeyStorePassword());
+ assertEquals("JKS", conf.getKeyStoreType());
+ assertEquals("/ts.p12", conf.getTrustStore());
+ assertEquals("tspass", conf.getTrustStorePassword());
+ assertEquals("PKCS12", conf.getTrustStoreType());
+ assertEquals("NONSTRICT", conf.getVerifyCertificates());
+ assertTrue(conf.isVerbose());
+ assertFalse(conf.isHelpRequested());
+ }
+ }
}
From bbfee62409c27fed0b8c8f9911aa093e9d8fcc7a Mon Sep 17 00:00:00 2001
From: hrishikesh-nalawade
Date: Wed, 3 Jun 2026 17:52:53 +0530
Subject: [PATCH 28/28] Parameterized Test
Signed-off-by: hrishikesh-nalawade
---
.../zowe/apiml/JwkEndpointCheckerTest.java | 30 +++++++------------
1 file changed, 10 insertions(+), 20 deletions(-)
diff --git a/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java b/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
index 0e2525d6a1..93f39af3ef 100644
--- a/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
+++ b/zosmf-jwt-check/src/test/java/org/zowe/apiml/JwkEndpointCheckerTest.java
@@ -170,28 +170,18 @@ void verbosePrintsResponseBody() throws IOException {
@Nested
class ValidateJwkBody {
- @Test
- void nullBodyIsFailure() throws IOException {
- when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(200, null));
- JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
- assertFalse(checker.check());
- assertTrue(errStream.toString().contains("empty response body"));
- }
-
- @Test
- void emptyBodyIsFailure() throws IOException {
- when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(200, ""));
- JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
- assertFalse(checker.check());
- assertTrue(errStream.toString().contains("empty response body"));
- }
-
- @Test
- void bodyWithoutNKeyIsFailure() throws IOException {
- when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(200, "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\"}]}"));
+ @ParameterizedTest
+ @CsvSource(value = {
+ "NULL_BODY | empty response body",
+ "EMPTY_BODY | empty response body",
+ "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\"}]} | does not contain an RSA modulus"
+ }, delimiter = '|')
+ void invalidBodiesAreFailures(String body, String expectedError) throws IOException {
+ String resolvedBody = "NULL_BODY".equals(body.trim()) ? null : "EMPTY_BODY".equals(body.trim()) ? "" : body.trim();
+ when(mockClient.executeCall(any(), anyMap())).thenReturn(new HttpClientWrapper.Response(200, resolvedBody));
JwkEndpointChecker checker = new JwkEndpointChecker(mockClient, mockConf);
assertFalse(checker.check());
- assertTrue(errStream.toString().contains("does not contain an RSA modulus"));
+ assertTrue(errStream.toString().contains(expectedError.trim()));
}
}