[java-identity-provider] 02/02: JPAR-182 Signature checking on WAR contents
Rod Widdowson
rdw at steadingsoftware.com
Sat Jul 31 14:26:22 UTC 2021
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=2930b2c9a3f5d40571ba09a5638b8a463e5ab2d8
commit 2930b2c9a3f5d40571ba09a5638b8a463e5ab2d8
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Sat Jul 31 14:59:20 2021 +0100
JPAR-182 Signature checking on WAR contents
https://issues.shibboleth.net/jira/browse/JPAR-182
The needs of the war checking and the plugin handler have diverged
enough that it makes sense to separate the gpg interface classes.
The plugin one goes back to what it was.
Dependency checking uses a substantially cut down clone which is
renamed "KeyRing" and all uses of the word truststore have been exised.
---
.../idp/installer/plugin/impl/TrustStore.java | 88 +------
.../idp/dependencies/DependencyTest.java | 58 ++---
.../shibboleth/idp/dependencies/GPGKeyRing.java | 257 +++++++++++++++++++++
3 files changed, 286 insertions(+), 117 deletions(-)
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
index 61119c67d..6aba2cba3 100644
--- 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
@@ -25,9 +25,7 @@ import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
import java.util.Iterator;
-import java.util.Set;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
@@ -70,12 +68,6 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
/** Explicit path to trust store. */
@NonnullAfterInit private String explicitTrustStore;
- /** Explicit stream for trust store. */
- @NonnullAfterInit private InputStream explicitTrustStoreStream;
-
- /** Explicit stream for trust store. */
- @NonnullAfterInit private InputStream explicitKeyStoreStream;
-
/** The plugin this is the trust store for. */
@NonnullAfterInit private String pluginId;
@@ -83,7 +75,7 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
@NonnullAfterInit private Path store;
/** The key store backup. */
- @Nullable private Path backup;
+ @NonnullAfterInit private Path backup;
/** KeyRing. */
@NonnullAfterInit private PGPPublicKeyRingCollection keyRings;
@@ -114,22 +106,6 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
explicitTrustStore = what;
}
- /** Set {@link #explicitTrustStoreStream}.
- * @param what The value to set.
- */
- public void setTrustStore(@Nullable final InputStream what) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- explicitTrustStoreStream = what;
- }
-
- /** Set {@link #explicitKeyStoreStream}.
- * @param what The value to set.
- */
- public void setKeyStore(@Nullable final InputStream what) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- explicitKeyStoreStream = what;
- }
-
/** Return a store loaded from the supplied stream.
*
* @param in the stream
@@ -203,10 +179,7 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
* @throws IOException from {@link Files#newOutputStream(Path, java.nio.file.OpenOption...)} and
* from {@link PGPPublicKeyRingCollection#encode(OutputStream)}
*/
- private void saveStoreInternal() throws IOException {
- if (backup == null) {
- throw new IOException("Cannot save this store");
- }
+ public void saveStoreInternal() throws IOException {
if (Files.exists(store)) {
Files.copy(store, backup, StandardCopyOption.REPLACE_EXISTING);
}
@@ -229,38 +202,6 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
}
}
- /** Lookup and return the key information for this key (and any parent).
- * @param sigForKey the signature to lookup
- * @return the string in a normalized form.
- */
- public String getKeyInfo(final Signature sigForKey) {
- final PGPPublicKeyRing keyRing;
- try {
- keyRing = keyRings.getPublicKeyRing(sigForKey.getSignature().getKeyID());
- } catch (final PGPException e) {
- log.warn("Couldn't locate key", e);
- return null;
- }
- if (keyRing == null) {
- log.info("Provided key stream did not contain a key for {}", sigForKey);
- return null;
- }
- final StringBuilder builder = new StringBuilder("KeyId: ").append(sigForKey.toString());
- final Iterator<PGPPublicKey> keyIterator = keyRing.getPublicKeys();
- final Set<String> seenNames = new HashSet<>();
- while (keyIterator.hasNext()) {
- final PGPPublicKey key = keyIterator.next();
- final Iterator<String> namesIterator = key.getUserIDs();
- while (namesIterator.hasNext()) {
- final String name = namesIterator.next();
- if (seenNames.add(name)) {
- builder.append("\tUsername:\t").append(name);
- }
- }
- }
- return builder.toString();
- }
-
/** 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.
*
@@ -363,31 +304,14 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
}
/** {@inheritDoc} */
- // CheckStyle: CyclomaticComplexity OFF
protected void doInitialize() throws ComponentInitializationException {
super.doInitialize();
- if (pluginId == null && explicitTrustStoreStream == null && explicitKeyStoreStream == null) {
+ if (pluginId == null) {
throw new ComponentInitializationException("Plugin Id not set up");
}
- if (explicitKeyStoreStream != null) {
- try {
- keyRings = new PGPPublicKeyRingCollection(explicitKeyStoreStream, new JcaKeyFingerprintCalculator());
- } catch (final IOException | PGPException e) {
- e.printStackTrace();
- throw new ComponentInitializationException(e);
- }
-
- } else if (explicitTrustStoreStream != null) {
- try {
- keyRings = loadStoreFrom(explicitTrustStoreStream);
- } catch (final IOException e) {
- log.error("Plugin {}: Could not load explicit trust store from stream", e);
- throw new ComponentInitializationException(e);
- }
- backup = null;
- } else if (explicitTrustStore != null) {
+ if (explicitTrustStore != null) {
store = Path.of(explicitTrustStore);
if (!Files.exists(store)) {
log.error("Trust store {} does not exist", explicitTrustStore);
@@ -430,7 +354,7 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
}
}
}
- // CheckStyle: CyclomaticComplexity ON
+
/**
* An opaque handle around a {@link PGPSignature}.
*/
@@ -460,7 +384,7 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
throw new IOException("Provided file was not a signature");
}
}
- keyId = String.format("0X%016X", signature.getKeyID());
+ keyId = String.format("0X%X", signature.getKeyID());
}
/**
diff --git a/idp-installer/src/test/java/net/shibboleth/idp/dependencies/DependencyTest.java b/idp-installer/src/test/java/net/shibboleth/idp/dependencies/DependencyTest.java
index 9502665cc..de74cd9db 100644
--- a/idp-installer/src/test/java/net/shibboleth/idp/dependencies/DependencyTest.java
+++ b/idp-installer/src/test/java/net/shibboleth/idp/dependencies/DependencyTest.java
@@ -66,12 +66,10 @@ import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import net.shibboleth.ext.spring.resource.HTTPResource;
+import net.shibboleth.idp.dependencies.GPGKeyRing.Signature;
import net.shibboleth.idp.dependencies.ParsedPom.PomArtifact;
import net.shibboleth.idp.installer.plugin.impl.PluginInstallerSupport;
-import net.shibboleth.idp.installer.plugin.impl.TrustStore;
-import net.shibboleth.idp.installer.plugin.impl.TrustStore.Signature;
import net.shibboleth.utilities.java.support.collection.Pair;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.httpclient.HttpClientBuilder;
import net.shibboleth.utilities.java.support.xml.ParserPool;
@@ -101,8 +99,8 @@ public class DependencyTest extends OpenSAMLInitBaseTestCase implements PomLoade
/** where we are writing to (target/dependencyReport.txt).*/
private PrintWriter report;
- /** The trust stores for our signature test. */
- private final Map<String, Optional<TrustStore>> trustStrores = new HashMap<>();
+ /** The key rings for our signature test. */
+ private final Map<String, Optional<GPGKeyRing>> keyRings = new HashMap<>();
/** The ArtefactId to GroupId mapping */
private final Map<String, String> artifactToGroup = new HashMap<>();
@@ -299,9 +297,9 @@ public class DependencyTest extends OpenSAMLInitBaseTestCase implements PomLoade
report.format("%-30s: %-14s Snapshot version on a snapshot build. Not Checked\n", name.getFirst(), name.getSecond());
return 0;
}
- final TrustStore store = getTrustStore(group);
- if (store == null) {
- report.format("%-30s: %-14s No truststore for group %s\n", name.getFirst(), name.getSecond(), group);
+ final GPGKeyRing keyRing = getKeyRing(group);
+ if (keyRing == null) {
+ report.format("%-30s: %-14s No keyring for group %s\n", name.getFirst(), name.getSecond(), group);
return 1;
}
final Signature sig = getSignature(jarAsArtifact);
@@ -310,23 +308,23 @@ public class DependencyTest extends OpenSAMLInitBaseTestCase implements PomLoade
name.getFirst(), name.getSecond(), group);
return 1;
}
- if (!store.contains(sig)) {
- report.format("%-30s: %-14s KeyId (%s) not found in truststore for %s\n", name.getFirst(), name.getSecond(), sig.toString(), group);
+ if (!keyRing.contains(sig)) {
+ report.format("%-30s: %-14s KeyId (%s) not found in keyring for %s\n", name.getFirst(), name.getSecond(), sig.toString(), group);
return 1;
}
try (final BufferedInputStream stream = new BufferedInputStream(new FileInputStream(jarFile.toFile()))) {
- if (!store.checkSignature(stream, sig)) {
- report.format("%-30s: %-14s Signature Mismatch : %s in Trustore %s\n",
- name.getFirst(), name.getSecond(), store.getKeyInfo(sig), group);
+ if (!keyRing.checkSignature(stream, sig)) {
+ report.format("%-30s: %-14s Signature Mismatch : %s in keyring %s\n",
+ name.getFirst(), name.getSecond(), keyRing.getKeyInfo(sig), group);
return 1;
}
} catch (IOException e) {
e.printStackTrace();
return 1;
}
- report.format("%-30s: %-14s Signature Match in trustore %s : %s \n",
- name.getFirst(), name.getSecond(), group, store.getKeyInfo(sig));
+ report.format("%-30s: %-14s Signature Match in keyring %s : %s \n",
+ name.getFirst(), name.getSecond(), group, keyRing.getKeyInfo(sig));
return 0;
}
@@ -346,7 +344,7 @@ public class DependencyTest extends OpenSAMLInitBaseTestCase implements PomLoade
return null;
}
try (final InputStream stream = new BufferedInputStream(new FileInputStream(path.toFile()))){
- return TrustStore.signatureOf(stream);
+ return GPGKeyRing.signatureOf(stream);
}
catch (IOException e) {
e.printStackTrace();
@@ -354,12 +352,12 @@ public class DependencyTest extends OpenSAMLInitBaseTestCase implements PomLoade
}
}
- /** Locate the truststore in the cache or load & cache it (or a negative lookup).
+ /** Locate the keyring in the cache or load & cache it (or a negative lookup).
* @param group the group to load
- * @return a truststore or null if there wasn't one.
+ * @return a keyring or null if there wasn't one.
*/
- private TrustStore getTrustStore(final String group) {
- final Optional<TrustStore> opt = trustStrores.get(group);
+ private GPGKeyRing getKeyRing(final String group) {
+ final Optional<GPGKeyRing> opt = keyRings.get(group);
if (opt != null) {
if (opt.isEmpty()) {
return null;
@@ -367,22 +365,12 @@ public class DependencyTest extends OpenSAMLInitBaseTestCase implements PomLoade
return opt.get();
}
- try (final InputStream input = getClass().getResourceAsStream("/net/shibboleth/idp/dependencies/stores/"+group);
- final InputStream keyStore = getClass().getResourceAsStream("/net/shibboleth/idp/dependencies/stores/"+group+".gpg")) {
- final TrustStore store = new TrustStore();
- if (keyStore != null) {
- store.setKeyStore(keyStore);
- } else if (input != null) {
- store.setTrustStore(input);
- } else {
- trustStrores.put(group, Optional.empty());
- return null;
- }
- store.initialize();
- trustStrores.put(group, Optional.of(store));
+ try {
+ final GPGKeyRing store = new GPGKeyRing(group);
+ keyRings.put(group, Optional.of(store));
return store;
- } catch (IOException | ComponentInitializationException e) {
- e.printStackTrace();
+ } catch (Exception e) {
+ keyRings.put(group, Optional.empty());
return null;
}
}
diff --git a/idp-installer/src/test/java/net/shibboleth/idp/dependencies/GPGKeyRing.java b/idp-installer/src/test/java/net/shibboleth/idp/dependencies/GPGKeyRing.java
new file mode 100644
index 000000000..a3629ea93
--- /dev/null
+++ b/idp-installer/src/test/java/net/shibboleth/idp/dependencies/GPGKeyRing.java
@@ -0,0 +1,257 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.dependencies;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
+
+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.slf4j.Logger;
+
+import net.shibboleth.idp.installer.impl.InstallationLogger;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+
+/**
+ * Code to handle (load, update, check) the keyrings for each maven group.
+ */
+ at NotThreadSafe public final class GPGKeyRing {
+
+ /** logger. */
+ @Nonnull private final Logger log = InstallationLogger.getLogger(GPGKeyRing.class);
+
+ /** The key store backup. */
+ @Nullable private Path backup;
+
+ /** KeyRing. */
+ @NonnullAfterInit private PGPPublicKeyRingCollection keyRings;
+
+ /** Constructor.
+ * Locate and load the keyring for the provided group, First look for the keyring
+ * and then for an asc file.
+ * @param group the group to look for
+ * @throws Exception under various error conditions.
+ */
+ public GPGKeyRing(final String group) throws Exception {
+ try (final InputStream armoredKeys = getClass().getResourceAsStream("/net/shibboleth/idp/dependencies/stores/"+group);
+ final InputStream keyRingStream = getClass().getResourceAsStream("/net/shibboleth/idp/dependencies/stores/"+group+".gpg")) {
+ if (keyRingStream != null) {
+ log.debug("Loading keyring for {}", group);
+ keyRings = new PGPPublicKeyRingCollection(keyRingStream, new JcaKeyFingerprintCalculator());
+ } else if (armoredKeys != null) {
+ log.debug("Loading asci keys for {}", group);
+ keyRings = loadRingFromAsc(armoredKeys);
+ } else {
+ log.warn("No asc of keyring found for {}", group);
+ throw new FileNotFoundException("Could not locate keyring");
+ }
+ } catch (final Exception e) {
+ log.error("Could not load explicit trust store for {} from stream", group, e);
+ throw e;
+ }
+ }
+
+ /** 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 loadRingFromAsc(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);
+ } catch (final PGPException e) {
+ throw new IOException("Error reading key ring", e);
+ }
+ }
+
+ /** Lookup and return the key information for this key (and any parent).
+ * @param sigForKey the signature to lookup
+ * @return the string in a normalized form.
+ */
+ protected String getKeyInfo(final Signature sigForKey) {
+ final PGPPublicKeyRing keyRing;
+ try {
+ keyRing = keyRings.getPublicKeyRing(sigForKey.getSignature().getKeyID());
+ } catch (final PGPException e) {
+ log.warn("Couldn't locate key", e);
+ return null;
+ }
+ if (keyRing == null) {
+ log.info("Provided key stream did not contain a key for {}", sigForKey);
+ return null;
+ }
+ final StringBuilder builder = new StringBuilder("KeyId: ").append(sigForKey.toString());
+ final Iterator<PGPPublicKey> keyIterator = keyRing.getPublicKeys();
+ final Set<String> seenNames = new HashSet<>();
+ while (keyIterator.hasNext()) {
+ final PGPPublicKey key = keyIterator.next();
+ final Iterator<String> namesIterator = key.getUserIDs();
+ while (namesIterator.hasNext()) {
+ final String name = namesIterator.next();
+ if (seenNames.add(name)) {
+ builder.append("\tUsername:\t").append(name);
+ }
+ }
+ }
+ return builder.toString();
+ }
+
+ /** 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
+ */
+ protected static Signature signatureOf(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
+ */
+ protected boolean contains(final Signature signature) {
+
+ final PGPSignature sig = signature.getSignature();
+
+ log.debug("Looking for key with Id {}", signature);
+
+ try {
+ return keyRings.getPublicKey(sig.getKeyID()) != null;
+ } catch (final PGPException e) {
+ log.warn("Error looking for key {}", signature, e);
+ return false;
+ }
+ }
+
+ /** 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
+ */
+ protected boolean checkSignature(final InputStream input, 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;
+ }
+ }
+
+
+ /**
+ * 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(final @Nonnull InputStream input) throws IOException {
+ try (final InputStream sigStream = PGPUtil.getDecoderStream(input)) {
+ final JcaPGPObjectFactory factory = new JcaPGPObjectFactory(sigStream);
+ final Object first = factory.nextObject();
+ if (first instanceof PGPSignatureList) {
+ final PGPSignatureList list = (PGPSignatureList) first;
+ signature = list.get(0);
+ } else {
+ throw new IOException("Provided file was not a signature");
+ }
+ }
+ keyId = String.format("0X%016X", signature.getKeyID());
+ }
+
+ /**
+ * Get signature.
+ *
+ * @return the signature
+ */
+ protected PGPSignature getSignature() {
+ return signature;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return keyId;
+ }
+ }
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list