Skip to content

Add support for adding additional identity material at runtime #655

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

Hakky54
Copy link
Owner

@Hakky54 Hakky54 commented Jun 30, 2025

No description provided.

@Hakky54 Hakky54 linked an issue Jun 30, 2025 that may be closed by this pull request
@Hakky54
Copy link
Owner Author

Hakky54 commented Jun 30, 2025

Hi @nfalco79 I have created an initial implementation of your feature request. Can you maybe give it a try on your side if that is possible.

You need to checkout the poject, switch to the feature branch, run mvn install and add the snapshot version to your project.
The bare minimum to try out your case would be something like this:

SSLFactory sslFactory = SSLFactory.builder()
        .withDummyIdentityMaterial()
        .withInflatableIdentityMaterial()
        .build();

KeyStore keyStore = null; // your additional keystore file

KeyManagerUtils.addIdentityMaterial(sslFactory.getKeyManager().get(), keyStore, "password".toCharArray());
KeyManagerUtils.addIdentityRoute(sslFactory.getKeyManager().get(), "some-alias", "https://google.com:443");

You can also clear & override the the identity routes with the following snippet:

KeyManagerUtils.overrideIdentityRoute(sslFactory.getKeyManager().get(), "some-alias", "https://google.com:443");

Can you give it a try and share your results or give feedback?

@nfalco79
Copy link

nfalco79 commented Jun 30, 2025

Hi, i will try to apply this SNAPSHOT branch to the jenkins plugin.

Premise: I am not an expert in SSL, most of the time I limited myself to installing self-signed certificates on the servers or at most importing the public/private keys into some software to sign some content.

Looking at the methods you have exposed I have a doubt. I thought that the host routes were associated with a specific KeyStore (in fact in my example the method took 3 parameters, one of which was the host route), so based on the host towards which to perform the handlesnake, the associated KeyStore was retrieved. I also thought that the alias was any string key of my choice to retrieve a host route or the associated keystore, but perhaps I did not understand what they are for and how to use them.
Just to give an example:

SSLFactory.builder()
    .withInflatableTrustMaterial()
    .withIdentityMaterial(acmeKeyStore, "password1".toCharArray())
    .withIdentityRoute("x-any-string", "https://acme.com")
    .withIdentityMaterial(acmeKeyStore, "password2".toCharArray())
    .withIdentityRoute("y-any-string", "https://example.com")
    .build();

if I have two different keystores, one to perform mTSL to the acme.com server and the other to the example.com server, how does the SSLContext chose which of the 2 registered keystores to use to handlesnake to acme.com?

You can also clear & override the the identity routes with the following snippet:

KeyManagerUtils.overrideIdentityRoute(sslFactory.getKeyManager().get(), "some-alias", "https://google.com:443");

To clear do you mean KeyManagerUtils.overrideIdentityRoute(sslFactory.getKeyManager().get(), null, null); ?

@Hakky54
Copy link
Owner Author

Hakky54 commented Jun 30, 2025

I thought that the host routes were associated with a specific KeyStore (in fact in my example the method took 3 parameters, one of which was the host route), so based on the host towards which to perform the handlesnake, the associated KeyStore was retrieved. I also thought that the alias was any string key of my choice to retrieve a host route or the associated keystore, but perhaps I did not understand what they are for and how to use them.

I agree, it might be confusing. I attempted to use self explaining method names next to the documentation in the readme, but it might be not sufficient enough. To understand this concept, maybe I should explain the limitation first. By default with the classes and utilities in the JDK it is not possible to use multiple keymanagers. If you have a keystore with multiple keys and you instantiate a keymanager out of it, it will use the first matching key entry from the keystore and always use that one during the lifecycle of the application. To bypass this limitation I created a special KeyManager type, the AggregatedX509ExtendedKeyManager which can hold any amount of KeyManager.

Although this special KeyManager has the capability of having multiple KeyManagers, it is not smart enough to decide which key entry it should use. You need to provide the context/additional information. This is the place where the identity routes will be used. Identity route is just a mapping of a key entry to target server url. So when defining an identity route you need to pass the name of the key entry in the keystore also known as alias and the server url, so the http client will use that specifc key entry to the specified server target. It cannot be any randome text, it should be exact matching to what is defined in the keystore.

Let me try to illustrate screenshots. I have two different keystores. It has a key entry with alias client-one and in the other keystore it has a key entry with alias client-two., see here:
Screenshot 2025-06-30 at 22 08 42
Screenshot 2025-06-30 at 22 08 46

I create an sslfactory with these two keystores to use it for mutual authentication from the client side. I use the following code snippet as an example:

Map<String, List<String>> clientAliasesToHosts = new HashMap<>();
clientAliasesToHosts.put("client-one", Collections.singletonList("https://localhost:8443/api/hello"));
clientAliasesToHosts.put("client-two", Collections.singletonList("https://localhost:8444/api/hello"));

SSLFactory sslFactoryForClient = SSLFactory.builder()
        .withIdentityMaterial("keystore/client-server/client-one/identity.jks", keyStorePassword)
        .withIdentityMaterial("keystore/client-server/client-two/identity.jks", keyStorePassword)
        .withTrustMaterial("keystore/client-server/client-one/truststore.jks", keyStorePassword)
        .withTrustMaterial("keystore/client-server/client-two/truststore.jks", keyStorePassword)
        .withIdentityRoute(clientAliasesToHosts)
        .build();

As you can see from the above example I am adding identity routes. I define which key entry from the keystore should be used when the client tries to communicate with the server. In this case key entry with alias client-one should be used when communicating with the server on localhost:8443 and client-two when communicating with localhost:8444

Let me include a screen shot from my debugger so you can corrolate it better:
Screenshot 2025-06-30 at 22 04 51

if I have two different keystores, one to perform mTSL to the acme.com server and the other to the example.com server, how does the SSLContext chose which of the 2 registered keystores to use to handlesnake to acme.com?

If you provide the correct identity route, the alias name in the keystore with the target server url it should work. Normally the default jdk keymanager just uses the first key entry from the keystore, but with this identity route you can use multiple keys for different target server urls without the need of having separate sslcontext of each http client communicating with a different server.

To clear do you mean KeyManagerUtils.overrideIdentityRoute(sslFactory.getKeyManager().get(), null, null); ?

With that option you can just only override an existing route to point to a different target server url.
I hope it makes a bit more sense now.

@nfalco79
Copy link

nfalco79 commented Jul 3, 2025

thank you very much for the explanation, now it is clear how the alias is used. So before I can add an identity material I must first extract the aliases from the keystore, because in my case I do not know them in advance.
I'm testing this PR here jenkinsci/bitbucket-branch-source-plugin#1078

The piece of code is simply

    public void configureContext(SSLFactory sslFactory, HttpHost host) {
        sslFactory.getKeyManager().ifPresent(baseKeyManager -> {
            for (String alias : KeyStoreUtils.getAliases(keyStore)) {
                KeyManagerUtils.addIdentityMaterial(baseKeyManager, keyStore, Secret.toString(password).toCharArray());
                KeyManagerUtils.addIdentityRoute(baseKeyManager, alias, host.toString());
                logger.info(() -> "Add new identity material (keyStore) " + alias + " to the SSLContext.");
            }
        });
    }

it works but after a few call I notice that it does not check if the identity material exists or not so this is after two rest call
immagine

and this after I scan again a jenkins project

immagine

There are no API to check if an indentity material has been already registered, and there not API to override existing one.

I see another potential issue, if a user provide different keyStore to use against different servers they could use the same alias. Users do not know that alias must be unique in the Jenkins application and Jenkins could provide multiple company if provided as a SaaS service.

@Hakky54
Copy link
Owner Author

Hakky54 commented Jul 3, 2025

Thank you for testing it on your side, I will address all of your remarks this evening as it makes sense. You dont want to keep adding keymanager unconditionally and want some mechanism to check whether it is already present and conditionally ovverride it. I will keep you posted!

@Hakky54
Copy link
Owner Author

Hakky54 commented Jul 3, 2025

@nfalco79 I added new methods, which should do the trick for you. I added these:

KeyManagerUtils.addIdentityMaterial
KeyManagerUtils.removeIdentityMaterial
KeyManagerUtils.getAliases

With get alias you can check. whether the keymanager with the alias name is already present. You can add new or ovveride existing ones with the addIdentityMaterial, and remove the one with removeIdentityMaterial.

Can you give it a try with the latest commit from this pr?

@Hakky54
Copy link
Owner Author

Hakky54 commented Jul 14, 2025

There are no API to check if an indentity material has been already registered, and there not API to override existing one.

I have added a new function to get the mapped aliases, see here https://github.com/Hakky54/sslcontext-kickstart/pull/655/files#diff-edac107cac3e21aab77f6d1a5ea528c69a0ab7d57a5cdab1054893d32652f549R476

It uses a Map So if the key is already present the value will be overridden.

I see another potential issue, if a user provide different keyStore to use against different servers they could use the same alias. Users do not know that alias must be unique in the Jenkins application and Jenkins could provide multiple company if provided as a SaaS service.

I think this is the limitation of using multiple keystores as identity material. I will update the readme to address this limitation and mention that the alias across all keystores should be unique.

Copy link

@Hakky54 Hakky54 merged commit 9fa59ab into master Jul 14, 2025
14 checks passed
@Hakky54 Hakky54 deleted the feature/support-for-adding-additional-identity-matierial-at-runtime branch July 14, 2025 13:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add identityMaterial at runtime to an existing SSLContext
2 participants