[java-identity-provider] 02/02: JJETTY-13 Implement Jetty-base plugin V2

Rod Widdowson rdw at steadingsoftware.com
Wed Aug 6 18:55:55 UTC 2025


This is an automated email from the git hooks/post-receive script.

rdw pushed a commit to branch main
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=01984743c7f46b95ab39cc572d39860bf05b98f5

commit 01984743c7f46b95ab39cc572d39860bf05b98f5
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Wed Aug 6 19:52:41 2025 +0100

    JJETTY-13 Implement Jetty-base plugin V2
    
    https://shibboleth.atlassian.net/browse/JJETTY-13
    
    Jetty base now runs against IdP 5.1  This means adding two classes we moved into API
    for 5.2 back into impl.
---
 .../admin/impl/InstallableComponentGaugeSet.java   |   4 +-
 .../idp/installer/impl/InstallerProperties.java    |  84 +++++
 .../idp/installer/plugin/impl/TrustStore.java      | 398 +++++++++++++++++++++
 3 files changed, 484 insertions(+), 2 deletions(-)

diff --git a/idp-admin-impl/src/main/java/net/shibboleth/idp/admin/impl/InstallableComponentGaugeSet.java b/idp-admin-impl/src/main/java/net/shibboleth/idp/admin/impl/InstallableComponentGaugeSet.java
index 0ff21855c..dccb8676e 100644
--- a/idp-admin-impl/src/main/java/net/shibboleth/idp/admin/impl/InstallableComponentGaugeSet.java
+++ b/idp-admin-impl/src/main/java/net/shibboleth/idp/admin/impl/InstallableComponentGaugeSet.java
@@ -85,7 +85,7 @@ public class InstallableComponentGaugeSet extends AbstractIdentifiableInitializa
     
     /** Is update check enabled.*/
     private boolean updateCheckEnabled;
-    
+
     /** Constructor. */
     public InstallableComponentGaugeSet() {
 
@@ -118,7 +118,7 @@ public class InstallableComponentGaugeSet extends AbstractIdentifiableInitializa
     public void setUpdateCheckEnabled(boolean what) {
         updateCheckEnabled = what;
     }
-    
+
     /** 
      * Null safe IdP Version.
      * 
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/InstallerProperties.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/InstallerProperties.java
new file mode 100644
index 000000000..e304913db
--- /dev/null
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/InstallerProperties.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.installer.impl;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+
+/**
+ * Class to contain the publicly visible (and api) properties used by the installer. 
+ */
+ at Deprecated(forRemoval = true, since = "5.2")
+public final class InstallerProperties {
+    /** The name of a property file to fill in some or all of the above. This file is deleted after processing. */
+    @Nonnull @NotEmpty public static final String PROPERTY_SOURCE_FILE = "idp.property.file";
+
+    /** The name of a property file to merge with idp.properties. */
+    @Nonnull @NotEmpty public static final String IDP_PROPERTIES_MERGE = "idp.merge.properties";
+
+    /** The name of a property file to merge with ldap.properties. */
+    @Nonnull @NotEmpty public static final String LDAP_PROPERTIES_MERGE = "ldap.merge.properties";
+
+    /** The LDAP Password (usually associated with a username in ldap.properties). */
+    @Nonnull @NotEmpty public static final String LDAP_PASSWORD = "idp.LDAP.credential";
+
+    /** Where to install to.  Default is basedir */
+    @Nonnull @NotEmpty public static final String TARGET_DIR = "idp.target.dir";
+
+    /** The entity ID. */
+    @Nonnull @NotEmpty public static final String ENTITY_ID = "idp.entityID";
+
+    /** Do we  cause a failure rather than a prompt. */
+    @Nonnull @NotEmpty public static final String NO_PROMPT = "idp.noprompt";
+
+    /** What is the installer host name?  */
+    @Nonnull @NotEmpty public static final String HOST_NAME = "idp.host.name";
+
+    /** The scope to assert.  */
+    @Nonnull @NotEmpty public static final String SCOPE = "idp.scope";
+
+    /** The keystore password to use.  */
+    @Nonnull @NotEmpty public static final String KEY_STORE_PASSWORD = "idp.keystore.password";
+
+    /** The sealer password to use.  */
+    @Nonnull @NotEmpty public static final String SEALER_PASSWORD = "idp.sealer.password";
+
+    /** The sealer alias to use.  */
+    @Nonnull @NotEmpty public static final String SEALER_ALIAS = "idp.sealer.alias";
+
+    /** The keysize for the sealer.  */
+    @Nonnull @NotEmpty public static final String SEALER_KEYSIZE = "idp.sealer.keysize";
+
+    /** The the key size to generate.  */
+    @Nonnull @NotEmpty public static final String KEY_SIZE = "idp.keysize";
+
+    /** Mode to set on credential *key files. */
+    @Nonnull @NotEmpty public static final String MODE_CREDENTIAL_KEYS = "idp.conf.credentials.filemode";
+
+    /** Group to set on files in the credential and conf directories. */
+    @Nonnull @NotEmpty public static final String GROUP_CONF_CREDENTIALS = "idp.conf.credentials.group";
+
+    /** Do we do any chgrp/chmod work? */
+    @Nonnull @NotEmpty public static final String PERFORM_SET_MODE = "idp.conf.setmode";
+
+    /** Whether to tidy up after ourselves. */
+    @Nonnull @NotEmpty public static final String NO_TIDY = "idp.no.tidy";
+
+    /** Key size for all installer-generated keys. */
+    public static final int DEFAULT_KEY_SIZE = 3072;
+
+
+}
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/TrustStore.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/TrustStore.java
new file mode 100644
index 000000000..a5b129071
--- /dev/null
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/TrustStore.java
@@ -0,0 +1,398 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.installer.plugin.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
+
+import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureList;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
+import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
+import org.bouncycastle.util.encoders.Hex;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.installer.InstallerSupport;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.AbstractInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Code to handle (load, update, check) the trust store for an individual plugin.
+ * a thin shim on BC.
+ */
+ at Deprecated(forRemoval = true, since = "5.1")
+ at NotThreadSafe public final class TrustStore extends AbstractInitializableComponent {
+
+    /** logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(TrustStore.class);
+    
+    /** Where the IdP is installed.  */
+    @NonnullAfterInit private Path idpHome;
+    
+    /** Explicit path to trust store.  */
+    @NonnullAfterInit private String explicitTrustStore;
+
+    /** The plugin this is the trust store for.  NULL of this is general purpose usage. */
+    @Nullable private String pluginId;
+
+    /** The key store. */
+    @NonnullAfterInit private Path store;
+
+    /** The key store backup. */
+    @NonnullAfterInit private Path backup;
+
+    /** KeyRing. */
+    @NonnullAfterInit private PGPPublicKeyRingCollection keyRings;
+
+    /** Set the pluginId.
+     *
+     * @param what to set.
+     */
+    public void setPluginId(final String what) {
+        checkSetterPreconditions();
+        pluginId = what;
+    }
+
+    /** Set IdPHome.
+     *
+     * @param what The idpHome to set.
+     */
+    public void setIdpHome(@Nonnull final Path what) {
+        checkSetterPreconditions();
+        idpHome = what;
+    }
+
+    /** Set explicitTrustStore.
+    * @param what The value to set.
+    */
+   public void setTrustStore(@Nullable final String what) {
+       checkSetterPreconditions();
+       explicitTrustStore = what;
+   }
+
+    /** Return a store loaded from the supplied stream.
+     *
+     * @param in the stream
+     * @return a suitable store
+     * @throws IOException from {@link Files#newInputStream(Path, java.nio.file.OpenOption...)} and from
+     * {@link PGPPublicKeyRingCollection#PGPPublicKeyRingCollection(InputStream,
+     *   org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator)}
+     */
+    private static PGPPublicKeyRingCollection loadStoreFrom(@Nonnull final InputStream in) throws IOException {
+        try (final InputStream decoded = PGPUtil.getDecoderStream(in)) {
+           final ArrayList<PGPPublicKeyRing> listr = new ArrayList<>();
+
+           PGPObjectFactory pgpFact = new PGPObjectFactory(decoded, new JcaKeyFingerprintCalculator());
+           Object obj;
+           while ((obj = pgpFact.nextObject()) != null) {
+               // Inner loop - when new factories return nothing we are done
+               do {
+                   if (!(obj instanceof PGPPublicKeyRing)) {
+                       throw new IOException(obj.getClass().getName() + " found where PGPPublicKeyRing expected");
+                   }
+                   listr.add((PGPPublicKeyRing) obj);
+                   obj = pgpFact.nextObject();
+               } while (obj != null);
+               pgpFact = new PGPObjectFactory(decoded, new JcaKeyFingerprintCalculator());
+           }
+           return new PGPPublicKeyRingCollection(listr);
+       }
+    }
+
+    /** Load the store from its designated location.
+     *
+     * @throws IOException from {@link Files#newInputStream(Path, java.nio.file.OpenOption...)} and from
+     * {@link PGPPublicKeyRingCollection#PGPPublicKeyRingCollection(InputStream, 
+     *   org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator)} 
+     */
+    protected void loadStore() throws IOException {
+        try (final InputStream in = Files.newInputStream(store)) {
+            assert in != null;
+            keyRings = loadStoreFrom(in);
+        }
+    }
+
+    /** Create an empty store and save to new location.
+     *
+     * @throws IOException from {@link #saveStore()} and in the unlikely event that
+     *  {@link PGPPublicKeyRingCollection#PGPPublicKeyRingCollection(java.util.Collection)}
+     *  has problems.
+     */
+    protected void createNewStore() throws IOException {
+        keyRings = new PGPPublicKeyRingCollection(CollectionSupport.emptyList());
+        saveStoreInternal();
+    }
+
+    /** Save the store to its designated location.
+     *
+     * @throws IOException from {@link Files#newOutputStream(Path, java.nio.file.OpenOption...)} and
+     * from {@link PGPPublicKeyRingCollection#encode(OutputStream)}
+     */
+    public void saveStore() throws IOException {
+        checkComponentActive();
+        saveStoreInternal();
+    }
+
+    /** Save the store to its designated location.
+     *
+     * @throws IOException from {@link Files#newOutputStream(Path, java.nio.file.OpenOption...)} and
+     * from {@link PGPPublicKeyRingCollection#encode(OutputStream)}
+     */
+    public void saveStoreInternal() throws IOException {
+        if (Files.exists(store)) {
+            Files.copy(store, backup, StandardCopyOption.REPLACE_EXISTING);
+        }
+        try (final OutputStream outStream = Files.newOutputStream(store)) {
+            final Iterator<PGPPublicKeyRing> kit = keyRings.getKeyRings();
+            while (kit.hasNext()) {
+                final PGPPublicKey kr = kit.next().getPublicKey();
+
+                final StringBuffer comment = new StringBuffer().append("\n\r");
+                final Iterator<String> sit = kr.getUserIDs();
+                if (sit.hasNext()) {
+                    comment .append(sit.next()).append('\t');
+                }
+                comment.append("id\t").append(String.format("%X", (int) kr.getKeyID())).append("\n\r");
+                outStream.write(comment.toString().getBytes());
+                try (OutputStream armed = new ArmoredOutputStream(outStream)) {
+                    kr.encode(armed);
+                }
+            }
+        }
+    }
+    
+    /** Load up the provided store and if the key is found and the
+     * Predicate allows it add it to the store which we will then save.
+     *
+     * @param sigForKey the signature we are looking for a key for.
+     * @param keyStream where to load the key from
+     * @param accept whether we actually want to install this key
+     * @throws IOException if the load or save fails
+     */
+    public void importKeyFromStream(final Signature sigForKey,
+                            @Nonnull final InputStream keyStream,
+                            @Nonnull final Predicate<String> accept) throws IOException {
+        final PGPPublicKeyRingCollection providedStore = loadStoreFrom(keyStream);
+
+        final PGPPublicKey key = providedStore.getPublicKey(sigForKey.getSignature().getKeyID());
+        if (key == null) {
+            log.info("Provided key stream did not contain a key for {}", sigForKey);
+            return;
+        }
+        final StringBuilder builder = new StringBuilder("Signature:\t").
+                append(sigForKey.toString()).
+                append("\nFingerPrint:\t").
+                append((new String(Hex.encode(key.getFingerprint()))).toUpperCase());
+        final Iterator<String> namesIterator = key.getUserIDs();
+        while (namesIterator.hasNext()) {
+            builder.append("\nUsername:\t").append(namesIterator.next());
+        }
+        builder.append('\n');
+        final String keyInfo = builder.toString();
+        log.debug("Asking to import key\n{}", keyInfo);
+        if (!accept.test(keyInfo)) {
+            log.info("Key import barred by user");
+            return;
+        }
+        keyRings = PGPPublicKeyRingCollection.addPublicKeyRing(
+                keyRings,
+                new PGPPublicKeyRing(CollectionSupport.singletonList(key)));
+        saveStoreInternal();
+    }
+
+    /** Provide an opaque signature object from an input stream.
+     * @param stream what to read.
+     * @return the Signature.
+     * @throws IOException if there is a problem reading the file of it it doesn't represent a signature
+     */
+    @Nonnull public static Signature signatureOf(@Nonnull final InputStream stream) throws IOException {
+        return new Signature(stream);
+    }
+
+    /** Does the key that made this signature exist in our keyrings?
+     * @param signature what to ask about
+     * @return whether it is there
+     */
+    public boolean contains(@Nonnull final Signature signature) {
+
+        final PGPSignature sig = signature.getSignature();
+
+        log.debug("Looking for key with Id {}", signature);
+
+        return keyRings.getPublicKey(sig.getKeyID()) != null;
+    }
+
+    /** Run a signature check over the streams.
+     * @param input what to check
+     * @param signature what to check with
+     * @return whether it passed or not
+     * @throws IOException if we get an error reading the stream
+     */
+    public boolean checkSignature(@Nonnull final InputStream input, @Nonnull final Signature signature)
+            throws IOException {
+        try {
+            final PGPSignature pgpSignature = signature.getSignature();
+            final PGPPublicKey pubKey = keyRings.getPublicKey(pgpSignature.getKeyID());
+            pgpSignature.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), pubKey);
+
+            final byte[] buffer = new byte[1024];
+            int count = input.read(buffer);
+            while (count > 0) {
+                pgpSignature.update(buffer, 0, count);
+                count = input.read(buffer);
+            }
+            final boolean result = pgpSignature.verify();
+            if (result) {
+                log.debug("Signature Check Succeeded");
+            } else {
+                log.debug("Signature Check Failed");
+            }
+            return result;
+        } catch (final PGPException e) {
+            log.warn("Error thrown during signature check", e);
+            return false;
+        }
+    }
+    
+    /** {@inheritDoc} */
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+
+        if (explicitTrustStore != null) {
+            store = InstallerSupport.pathOf(explicitTrustStore);
+            if (!Files.exists(store)) {
+                log.error("Trust store {} does not exist", explicitTrustStore);
+                throw new ComponentInitializationException("Supplied trust store does not exist.");
+            }
+            backup = InstallerSupport.pathOf(explicitTrustStore + ".backup");
+            log.debug("Loading explicit truststore {}", explicitTrustStore);
+
+            try {
+                loadStore();
+            } catch (final IOException e) {
+                log.error("Could not load explicit trust store {}", explicitTrustStore, e);
+                throw new ComponentInitializationException(e);
+            }
+        } else {
+            if (pluginId == null) {
+                throw new ComponentInitializationException("Plugin Id not set up");
+            }
+
+            if (idpHome == null) {
+                throw new ComponentInitializationException("IdP home not set up");
+            }
+
+            if (!Files.exists(idpHome)) {
+                throw new ComponentInitializationException("IdP home '" + idpHome + "' does not exist");
+            }
+
+            try {
+                final Path parent = idpHome.resolve("credentials").resolve(pluginId);
+                if (!Files.exists(parent)) {
+                    log.info("Plugin {}: Trust store folder does not exist, creating", pluginId);
+                    Files.createDirectories(parent);
+                }
+                store = parent.resolve("truststore.asc");
+                backup = parent.resolve("truststore.asc.backup");
+                if (!Files.exists(store)) {
+                    log.info("Plugin {}: Trust store does not exist, creating", pluginId);
+                    createNewStore();
+                } else {
+                    log.debug("Plugin {}: Trust store exists, loading", pluginId);
+                    loadStore();
+                }
+            } catch (final IOException e) {
+                throw new ComponentInitializationException(e);
+            }
+        }
+    }
+    
+    /**
+     * An opaque handle around a {@link PGPSignature}.
+     */
+    public static final class Signature {
+        
+        /** What we are hiding. */
+        @Nonnull private PGPSignature signature;
+
+        /** printable key. */
+        @Nonnull private String keyId;
+
+        /**
+         * Constructor.
+         *
+         * @param input input data
+         * 
+         * @throws IOException if an error occurs
+         */
+        protected Signature(@Nonnull final InputStream input) throws IOException {
+            try (final InputStream sigStream =  PGPUtil.getDecoderStream(input)) {
+                final JcaPGPObjectFactory factory = new JcaPGPObjectFactory(sigStream);
+                final Object first = factory.nextObject();
+                if (first != null && first instanceof PGPSignatureList) {
+                    final PGPSignatureList list = (PGPSignatureList) first;
+                    if (list.isEmpty()) {
+                        throw new IOException("Provided signature file was empty");
+                    }
+                    signature = Constraint.isNotNull(list.get(0),
+                            "PGPSignatureList#get(0) retiurned null for non empty list");
+                } else {
+                    throw new IOException("Provided file was not a signature");
+                }
+            }
+            final String kid =String.format("0x%X", signature.getKeyID()) ;
+            assert kid != null;
+            keyId = kid;
+        }
+
+        /**
+         * Get signature.
+         * 
+         * @return the signature
+         */
+        @Nonnull protected PGPSignature getSignature() {
+            return signature;
+        }
+
+        /** {@inheritDoc} */
+        @Nonnull public String toString() {
+            return keyId;
+        }         
+    }
+
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list