Skip to content

Commit

Permalink
feat: java sample to authenticate with client certificate (#3862)
Browse files Browse the repository at this point in the history
* new sample app

Signed-off-by: ac892247 <[email protected]>

* unit test with https server

Signed-off-by: ac892247 <[email protected]>

* remove unused libs

Signed-off-by: ac892247 <[email protected]>

* remove unused params

Signed-off-by: ac892247 <[email protected]>

* fix checkstyle

Signed-off-by: ac892247 <[email protected]>

* downgrade undici for dev deps

Signed-off-by: ac892247 <[email protected]>

---------

Signed-off-by: ac892247 <[email protected]>
  • Loading branch information
achmelo authored Oct 20, 2024
1 parent 75cf96b commit 992deb3
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 4 deletions.
8 changes: 5 additions & 3 deletions api-catalog-ui/frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion api-catalog-ui/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@
"source-map-explorer": "2.5.3",
"start-server-and-test": "2.0.8",
"tmpl": "1.0.5",
"yaml": "2.6.0"
"yaml": "2.6.0",
"undici": "6.19.8"
},
"overrides": {
"nth-check": "2.1.1",
Expand Down
18 changes: 18 additions & 0 deletions client-cert-auth-sample/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
plugins {
id 'java'
}

group = 'org.zowe.apiml'
version = '3.0.39-SNAPSHOT'

repositories {
mavenCentral()
}

dependencies {
implementation libs.http.client5
}

test {
useJUnitPlatform()
}
81 changes: 81 additions & 0 deletions client-cert-auth-sample/src/main/java/org/zowe/apiml/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager;
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.core5.ssl.SSLContextBuilder;

import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.cert.Certificate;

public class Main {

private static final String API_URL = "https://localhost:8080/gateway/api/v1/auth/login"; // Replace with your API URL
private static final String CLIENT_CERT_PATH = "../keystore/client_cert/client-certs.p12"; // Replace with your client cert path
private static final String CLIENT_CERT_PASSWORD = "password"; // Replace with your cert password
private static final String CLIENT_CERT_ALIAS = "apimtst"; // Replace with your signed client cert alias
private static final String PRIVATE_KEY_ALIAS = "apimtst"; // Replace with your private key alias


public static void main(String[] args) {
try {
// Load the keystore containing the client certificate
KeyStore keyStore = KeyStore.getInstance("PKCS12");
try (FileInputStream keyStoreStream = new FileInputStream(new File(CLIENT_CERT_PATH))) {
keyStore.load(keyStoreStream, CLIENT_CERT_PASSWORD.toCharArray());
}

var key = keyStore.getKey(PRIVATE_KEY_ALIAS, CLIENT_CERT_PASSWORD.toCharArray()); // Load private key from original keystore
var cert = keyStore.getCertificate(CLIENT_CERT_ALIAS); // Load signed certificate from original keystore

// Create new keystore
var newKeyStore = KeyStore.getInstance("PKCS12");
newKeyStore.load(null);
newKeyStore.setKeyEntry(PRIVATE_KEY_ALIAS, key, CLIENT_CERT_PASSWORD.toCharArray(), new Certificate[]{cert}); // Create an entry with private key + signed certificate

// Create SSL context with the client certificate
var sslContext = new SSLContextBuilder().loadTrustMaterial((chain, type) -> true)
.loadKeyMaterial(newKeyStore, CLIENT_CERT_PASSWORD.toCharArray()).build();
var sslsf = new DefaultClientTlsStrategy(sslContext);


var connectionManager = BasicHttpClientConnectionManager.create((s) -> sslsf);

var clientBuilder = HttpClientBuilder.create().setConnectionManager(connectionManager);

try (var httpClient = clientBuilder.build()) {

// Create a POST request
var httpPost = new HttpPost(API_URL);

// Execute the request
var response = httpClient.execute(httpPost, res -> res);

// Print the response status
System.out.println("Response Code: " + response.getCode());

// Print headers
var headers = response.getHeaders();
for (var header : headers) {
System.out.println("Key : " + header.getName()
+ " ,Value : " + header.getValue());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
110 changes: 110 additions & 0 deletions client-cert-auth-sample/src/test/java/org/zowe/apiml/MainTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* 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.HttpsConfigurator;
import com.sun.net.httpserver.HttpsExchange;
import com.sun.net.httpserver.HttpsParameters;
import com.sun.net.httpserver.HttpsServer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.security.KeyStore;

import static org.junit.jupiter.api.Assertions.assertNotNull;

class MainTest {

static HttpsServer httpServer;
static AssertionError error;

@BeforeAll
static void setup() throws Exception {
var inetAddress = new InetSocketAddress("127.0.0.1", 8080);
httpServer = HttpsServer.create(inetAddress, 0);

var sslContext = SSLContext.getInstance("TLS");
var ks = KeyStore.getInstance("PKCS12");
try (var fis = new FileInputStream("../keystore/localhost/localhost.keystore.p12")) {
ks.load(fis, "password".toCharArray());
}
var km = KeyManagerFactory.getInstance("SunX509");
km.init(ks, "password".toCharArray());
var ts = KeyStore.getInstance("PKCS12");
try (var fis = new FileInputStream("../keystore/localhost/localhost.truststore.p12")) {
ts.load(fis, "password".toCharArray());
}
var tm = TrustManagerFactory.getInstance("SunX509");
tm.init(ts);

sslContext.init(km.getKeyManagers(), tm.getTrustManagers(), null);

var httpsConfigurator = new TestHttpsConfigurator(sslContext);
httpServer.setHttpsConfigurator(httpsConfigurator);
httpServer.createContext("/gateway/api/v1/auth/login", exchange -> {

exchange.sendResponseHeaders(204, 0);
var clientCert = ((HttpsExchange) exchange).getSSLSession().getPeerCertificates();
try {
// client certificate must be present at this stage
assertNotNull(clientCert);
} catch (AssertionError e) {
error = e;
}
exchange.close();
});
httpServer.start();

}

@AfterAll
static void tearDown() {

httpServer.stop(0);
if (error != null) {
throw error;
}
}

@Test
void givenHttpsRequestWithClientCertificate_thenPeerCertificateMustBeAvailable() {
// Assertion is done on the server to make sure that client certificate was delivered.
// Assertion error is then rethrown in the tear down method in case certificate was not present.
Main.main(null);
}

static class TestHttpsConfigurator extends HttpsConfigurator {
/**
* Creates a Https configuration, with the given {@link SSLContext}.
*
* @param context the {@code SSLContext} to use for this configurator
* @throws NullPointerException if no {@code SSLContext} supplied
*/
public TestHttpsConfigurator(SSLContext context) {
super(context);
}

@Override
public void configure(HttpsParameters params) {
var parms = getSSLContext().getDefaultSSLParameters();
parms.setNeedClientAuth(true);
params.setWantClientAuth(true);
params.setSSLParameters(parms);
}
}

}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,5 @@ include 'apiml-sample-extension'
include 'apiml-sample-extension-package'
include 'apiml-extension-loader'
include 'zowe-cli-id-federation-plugin'
include 'client-cert-auth-sample'

0 comments on commit 992deb3

Please sign in to comment.