summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas <>2023-08-21 16:49:20 +0200
committerThomas <>2023-08-21 16:49:20 +0200
commitf41a899539773146907eef25b459b4360719fd14 (patch)
treeca8cac40d2414415f904ef88b30febf483913ca0
parent958770eff456f5724e29166123c7e5c32391e3f4 (diff)
downloadEAAF-Components-f41a899539773146907eef25b459b4360719fd14.tar.gz
EAAF-Components-f41a899539773146907eef25b459b4360719fd14.tar.bz2
EAAF-Components-f41a899539773146907eef25b459b4360719fd14.zip
feat(sl20): add basic certificate-validity check into JWS validation
The check can be disabled by using the configuration property: modules.sl20.security.truststore.need.valid.certificate
-rw-r--r--eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/JoseUtils.java138
-rw-r--r--eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/credentials/EaafKeyStoreFactoryTest.java2
-rw-r--r--eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/utils/JoseUtilsTest.java72
-rw-r--r--eaaf_core_utils/src/test/resources/data/junit.jksbin3980 -> 4781 bytes
-rw-r--r--eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/Constants.java2
-rw-r--r--eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java10
6 files changed, 174 insertions, 50 deletions
diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/JoseUtils.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/JoseUtils.java
index a67f3523..f3c8588d 100644
--- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/JoseUtils.java
+++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/JoseUtils.java
@@ -4,6 +4,7 @@ import java.io.IOException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyStore;
+import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.PublicKey;
@@ -16,6 +17,7 @@ import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Collections;
+import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -203,7 +205,7 @@ public class JoseUtils {
}
/**
- * Verify a JOSE signature.
+ * Verify a JOSE signature without signing certificate validation.
*
* @param serializedContent Serialized content that should be verified
* @param trustedCerts Trusted certificates that should be used for
@@ -216,6 +218,29 @@ public class JoseUtils {
public static JwsResult validateSignature(@Nonnull final String serializedContent,
@Nonnull final List<X509Certificate> trustedCerts, @Nonnull final AlgorithmConstraints constraints)
throws JoseException, IOException {
+ return validateSignature(serializedContent, trustedCerts, constraints, false);
+
+ }
+
+ /**
+ * Verify a JOSE signature.
+ *
+ * @param serializedContent Serialized content that should be
+ * verified
+ * @param trustedCerts Trusted certificates that should be
+ * used for verification
+ * @param constraints {@link AlgorithmConstraints} for
+ * verification
+ * @param needValidateSigningCertificate If <code>true</code> then the signing
+ * certificate itself must be valid
+ * @return {@link JwsResult} object
+ * @throws JoseException In case of a signature verification error
+ * @throws IOException In case of a general error
+ */
+ public static JwsResult validateSignature(@Nonnull final String serializedContent,
+ @Nonnull final List<X509Certificate> trustedCerts, @Nonnull final AlgorithmConstraints constraints,
+ final boolean needValidateSigningCertificate)
+ throws JoseException, IOException {
final JsonWebSignature jws = new JsonWebSignature();
// set payload
jws.setCompactSerialization(serializedContent);
@@ -224,66 +249,30 @@ public class JoseUtils {
jws.setAlgorithmConstraints(constraints);
// load signinc certs
- Key selectedKey = null;
- final List<X509Certificate> x5cCerts = jws.getCertificateChainHeaderValue();
- final String x5t256 = jws.getX509CertSha256ThumbprintHeaderValue();
- if (x5cCerts != null) {
- log.debug("Found x509 certificate in JOSE header ... ");
- log.trace("Sorting received X509 certificates ... ");
- final List<X509Certificate> sortedX5cCerts = X509Utils.sortCertificates(x5cCerts);
-
-
-
- if (trustedCerts.contains(sortedX5cCerts.get(0))) {
- selectedKey = sortedX5cCerts.get(0).getPublicKey();
-
- } else {
- log.info("Can NOT find JOSE certificate in truststore.");
- if (log.isDebugEnabled()) {
- try {
- log.debug("Cert: {}", Base64Utils.encodeToString(sortedX5cCerts.get(0).getEncoded()));
-
- } catch (final CertificateEncodingException e) {
- log.warn("Can not create DEBUG output", e);
-
- }
- }
- }
-
- } else if (StringUtils.isNotEmpty(x5t256)) {
- log.debug("Found x5t256 fingerprint in JOSE header .... ");
- final X509VerificationKeyResolver x509VerificationKeyResolver = new X509VerificationKeyResolver(
- trustedCerts);
- selectedKey = x509VerificationKeyResolver.resolveKey(jws, Collections.<JsonWebStructure>emptyList());
-
- } else {
- throw new JoseException("JWS contains NO signature certificate or NO certificate fingerprint");
+ Key selectedKey = selectSigningKey(jws, trustedCerts);
+ // validate signing-certificate constrains
+ if (needValidateSigningCertificate) {
+ validateSigningCertificate(selectedKey, trustedCerts);
}
- if (selectedKey == null) {
- throw new JoseException("Can NOT select verification key for JWS. Signature verification FAILED");
-
- }
-
- //set BouncyCastleProvider as default provider
+ // set BouncyCastleProvider as default provider
final ProviderContext providerCtx = new ProviderContext();
providerCtx.getGeneralProviderContext().setGeneralProvider(BouncyCastleProvider.PROVIDER_NAME);
jws.setProviderContext(providerCtx);
-
+
// set verification key
jws.setKey(convertToBcKeyIfRequired(selectedKey));
-
+
// load payLoad
return new JwsResult(
jws.verifySignature(),
jws.getUnverifiedPayload(),
jws.getHeaders(),
- x5cCerts);
+ jws.getCertificateChainHeaderValue());
}
-
/**
* Convert an ECC public-key into BouncyCastle implementation.
*
@@ -357,6 +346,65 @@ public class JoseUtils {
}
+ private static void validateSigningCertificate(Key selectedKey, List<X509Certificate> trustedCerts)
+ throws JoseException {
+ X509Certificate signingCert = trustedCerts.stream()
+ .filter(el -> MessageDigest.isEqual(el.getPublicKey().getEncoded(), selectedKey.getEncoded()))
+ .findFirst()
+ .get();
+
+ Date now = new Date();
+ if (now.before(signingCert.getNotBefore()) || now.after(signingCert.getNotAfter())) {
+ log.error("JOSE signing-certificate is not valid. Now: {}, Before: {}, After: {}",
+ now, signingCert.getNotBefore(), signingCert.getNotAfter());
+ throw new JoseException("JOSE signing-certificate is not in validity periode");
+
+ } else {
+ log.debug("JOSE signing-certificate is in validity periode");
+
+ }
+ }
+
+ private static Key selectSigningKey(JsonWebSignature jws, List<X509Certificate> trustedCerts)
+ throws JoseException {
+ final List<X509Certificate> x5cCerts = jws.getCertificateChainHeaderValue();
+ final String x5t256 = jws.getX509CertSha256ThumbprintHeaderValue();
+ if (x5cCerts != null) {
+ log.debug("Found x509 certificate in JOSE header ... ");
+ log.trace("Sorting received X509 certificates ... ");
+ final List<X509Certificate> sortedX5cCerts = X509Utils.sortCertificates(x5cCerts);
+
+ if (trustedCerts.contains(sortedX5cCerts.get(0))) {
+ return sortedX5cCerts.get(0).getPublicKey();
+
+ } else {
+ log.info("Can NOT find JOSE certificate in truststore.");
+ if (log.isDebugEnabled()) {
+ try {
+ log.debug("Cert: {}", Base64Utils.encodeToString(sortedX5cCerts.get(0).getEncoded()));
+
+ } catch (final CertificateEncodingException e) {
+ log.warn("Can not create DEBUG output", e);
+
+ }
+ }
+ }
+
+ } else if (StringUtils.isNotEmpty(x5t256)) {
+ log.debug("Found x5t256 fingerprint in JOSE header .... ");
+ final X509VerificationKeyResolver x509VerificationKeyResolver =
+ new X509VerificationKeyResolver(trustedCerts);
+ return x509VerificationKeyResolver.resolveKey(jws, Collections.<JsonWebStructure>emptyList());
+
+ } else {
+ throw new JoseException("JWS contains NO signature certificate or NO certificate fingerprint");
+
+ }
+
+ throw new JoseException("Can NOT select verification key for JWS. Signature verification FAILED");
+
+ }
+
private JoseUtils() {
}
diff --git a/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/credentials/EaafKeyStoreFactoryTest.java b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/credentials/EaafKeyStoreFactoryTest.java
index 0d3492a7..47dd4a11 100644
--- a/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/credentials/EaafKeyStoreFactoryTest.java
+++ b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/credentials/EaafKeyStoreFactoryTest.java
@@ -318,7 +318,7 @@ public class EaafKeyStoreFactoryTest {
//read trusted certs
final List<X509Certificate> trustedCerts = EaafKeyStoreUtils.readCertsFromKeyStore(keyStore.getFirst());
Assert.assertNotNull("Trusted certs", trustedCerts);
- Assert.assertEquals("Trusted certs size", 2, trustedCerts.size());
+ Assert.assertEquals("Trusted certs size", 3, trustedCerts.size());
//read priv. key
final Pair<Key, X509Certificate[]> privCred1 = EaafKeyStoreUtils.getPrivateKeyAndCertificates(
diff --git a/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/utils/JoseUtilsTest.java b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/utils/JoseUtilsTest.java
index 43002688..4b51c1ec 100644
--- a/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/utils/JoseUtilsTest.java
+++ b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/utils/JoseUtilsTest.java
@@ -1,7 +1,12 @@
package at.gv.egiz.eaaf.core.test.utils;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
import java.io.IOException;
+import java.security.KeyStore;
import java.security.NoSuchProviderException;
+import java.security.Provider;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
@@ -10,6 +15,7 @@ import java.util.Collections;
import java.util.List;
import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.RandomStringUtils;
import org.jose4j.jwa.AlgorithmConstraints;
import org.jose4j.jwa.AlgorithmConstraints.ConstraintType;
import org.jose4j.jws.AlgorithmIdentifiers;
@@ -19,14 +25,23 @@ import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.BlockJUnit4ClassRunner;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory;
+import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreUtils;
+import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration;
+import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType;
+import at.gv.egiz.eaaf.core.impl.data.Pair;
import at.gv.egiz.eaaf.core.impl.utils.JoseUtils;
import at.gv.egiz.eaaf.core.impl.utils.JoseUtils.JwsResult;
import iaik.security.ec.provider.ECCelerate;
import iaik.security.provider.IAIK;
+import lombok.SneakyThrows;
-@RunWith(BlockJUnit4ClassRunner.class)
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration("/spring/test_eaaf_pvp_lazy.beans.xml")
public class JoseUtilsTest {
private static final List<String> BINDING_AUTH_ALGORITHM_WHITELIST_SIGNING = Collections.unmodifiableList(
@@ -36,6 +51,13 @@ public class JoseUtilsTest {
AlgorithmIdentifiers.RSA_PSS_USING_SHA256,
AlgorithmIdentifiers.RSA_PSS_USING_SHA512));
+ private static final String PATH_TO_SOFTWARE_KEYSTORE_JKS =
+ "src/test/resources/data/junit.jks";
+ private static final String SOFTWARE_KEYSTORE_PASSWORD = "password";
+
+ @Autowired
+ EaafKeyStoreFactory keyStoreFactory;
+
/**
*jUnit test class initializer.
*/
@@ -81,4 +103,50 @@ public class JoseUtilsTest {
Assert.assertArrayEquals("Signercerts", trustedCert.getEncoded(), result.getX5cCerts().get(0).getEncoded());
}
+
+ @Test
+ public void verifyJwsInvalidCertificate() throws JoseException, IOException, CertificateException,
+ NoSuchProviderException {
+
+ final String serializedContent = IOUtils.toString(JoseUtils.class.getResourceAsStream(
+ "/data/bindingAuth1.jws"), "UTF-8");
+
+ final iaik.x509.X509Certificate trustedCert = new iaik.x509.X509Certificate(JoseUtils.class
+ .getResourceAsStream("/data/bindingAuth1.crt"));
+
+ final List<X509Certificate> trustedCerts = Arrays.asList(trustedCert);
+ final AlgorithmConstraints constraints = new AlgorithmConstraints(ConstraintType.PERMIT,
+ BINDING_AUTH_ALGORITHM_WHITELIST_SIGNING
+ .toArray(new String[BINDING_AUTH_ALGORITHM_WHITELIST_SIGNING.size()]));
+
+ JoseException error = assertThrows("wrong exception", JoseException.class,
+ () -> JoseUtils.validateSignature(serializedContent, trustedCerts, constraints, true));
+ assertEquals("JOSE signing-certificate is not in validity periode", error.getMessage());
+
+
+ }
+
+ @Test
+ @SneakyThrows
+ public void verifyJwsValidCertificate() throws JoseException, IOException, CertificateException,
+ NoSuchProviderException {
+
+ final KeyStoreConfiguration keyStoreConfig = new KeyStoreConfiguration();
+ keyStoreConfig.setKeyStoreType(KeyStoreType.JKS);
+ keyStoreConfig.setSoftKeyStoreFilePath(PATH_TO_SOFTWARE_KEYSTORE_JKS);
+ keyStoreConfig.setSoftKeyStorePassword(SOFTWARE_KEYSTORE_PASSWORD);
+
+ Pair<KeyStore, Provider> keyStore = keyStoreFactory.buildNewKeyStore(keyStoreConfig);
+
+ String jws = JoseUtils.createSignature(keyStore, "meta", "password".toCharArray(),
+ RandomStringUtils.randomAlphanumeric(10), false, "jUnit");
+
+ final AlgorithmConstraints constraints = new AlgorithmConstraints(ConstraintType.PERMIT,
+ BINDING_AUTH_ALGORITHM_WHITELIST_SIGNING
+ .toArray(new String[BINDING_AUTH_ALGORITHM_WHITELIST_SIGNING.size()]));
+ List<X509Certificate> trustedCertificates = EaafKeyStoreUtils.readCertsFromKeyStore(keyStore.getFirst());
+
+ JoseUtils.validateSignature(jws, trustedCertificates, constraints, true);
+
+ }
}
diff --git a/eaaf_core_utils/src/test/resources/data/junit.jks b/eaaf_core_utils/src/test/resources/data/junit.jks
index 59e6ad13..bf893a08 100644
--- a/eaaf_core_utils/src/test/resources/data/junit.jks
+++ b/eaaf_core_utils/src/test/resources/data/junit.jks
Binary files differ
diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/Constants.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/Constants.java
index 74d67d01..b454558a 100644
--- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/Constants.java
+++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/Constants.java
@@ -36,6 +36,8 @@ public class Constants {
CONFIG_PROP_PREFIX + ".security.truststore.path";
public static final String CONFIG_PROP_SECURITY_TRUSTSTORE_PASSWORD =
CONFIG_PROP_PREFIX + ".security.truststore.password";
+ public static final String CONFIG_PROP_SECURITY_TRUSTSTORE_NEED_VALID_CERTIFICATE =
+ CONFIG_PROP_PREFIX + ".security.truststore.need.valid.certificate";
public static final String CONFIG_PROP_SECURITY_SIG_ALG_RSA =
CONFIG_PROP_PREFIX + ".security.sigalg.rsa";
diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java
index 4e939d55..668ce09a 100644
--- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java
+++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java
@@ -154,8 +154,8 @@ public class JsonSecurityUtils implements IJoseTools {
public VerificationResult validateSignature(@Nonnull final String serializedContent,
@Nonnull final List<X509Certificate> trustedCerts, @Nonnull final AlgorithmConstraints constraints)
throws JoseException, IOException {
-
- final JwsResult result = JoseUtils.validateSignature(serializedContent, trustedCerts, constraints);
+ final JwsResult result = JoseUtils.validateSignature(serializedContent, trustedCerts, constraints,
+ isValidCertificateNeeded());
return new VerificationResult(
JsonMapper.getMapper().readTree(result.getFullJoseHeader().getFullHeaderAsJsonString()),
JsonMapper.getMapper().readTree(result.getPayLoad()),
@@ -413,4 +413,10 @@ public class JsonSecurityUtils implements IJoseTools {
}
+ private boolean isValidCertificateNeeded() {
+ return authConfig.getBasicConfigurationBoolean(
+ Constants.CONFIG_PROP_SECURITY_TRUSTSTORE_NEED_VALID_CERTIFICATE, true);
+
+ }
+
}