diff options
author | Thomas <> | 2023-08-21 16:49:20 +0200 |
---|---|---|
committer | Thomas <> | 2023-08-21 16:49:20 +0200 |
commit | f41a899539773146907eef25b459b4360719fd14 (patch) | |
tree | ca8cac40d2414415f904ef88b30febf483913ca0 | |
parent | 958770eff456f5724e29166123c7e5c32391e3f4 (diff) | |
download | EAAF-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
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 Binary files differindex 59e6ad13..bf893a08 100644 --- a/eaaf_core_utils/src/test/resources/data/junit.jks +++ b/eaaf_core_utils/src/test/resources/data/junit.jks 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); + + } + } |