From d781f0e89f16c650f70cc47d1ed5c4da2673b4d1 Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Thu, 11 Apr 2019 09:45:25 +0200 Subject: add EAAF module for authentication method based on Security-Layer 2.0 communication --- .../modules/auth/sl20/utils/JsonSecurityUtils.java | 394 +++++++++++++++++++++ 1 file changed, 394 insertions(+) create mode 100644 eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java (limited to 'eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java') 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 new file mode 100644 index 00000000..5eda95cc --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java @@ -0,0 +1,394 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.utils; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.security.Key; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.apache.commons.lang3.StringUtils; +import org.jose4j.jwa.AlgorithmConstraints; +import org.jose4j.jwa.AlgorithmConstraints.ConstraintType; +import org.jose4j.jwe.JsonWebEncryption; +import org.jose4j.jws.AlgorithmIdentifiers; +import org.jose4j.jws.JsonWebSignature; +import org.jose4j.jwx.JsonWebStructure; +import org.jose4j.keys.X509Util; +import org.jose4j.keys.resolvers.X509VerificationKeyResolver; +import org.jose4j.lang.JoseException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.Base64Utils; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonNode; + +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.exceptions.EAAFConfigurationException; +import at.gv.egiz.eaaf.core.impl.utils.FileUtils; +import at.gv.egiz.eaaf.core.impl.utils.KeyStoreUtils; +import at.gv.egiz.eaaf.core.impl.utils.X509Utils; +import at.gv.egiz.eaaf.modules.auth.sl20.Constants; +import at.gv.egiz.eaaf.modules.auth.sl20.data.VerificationResult; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SL20Exception; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SL20SecurityException; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SLCommandoBuildException; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SLCommandoParserException; + +@Service +public class JsonSecurityUtils implements IJOSETools{ + private static final Logger log = LoggerFactory.getLogger(JsonSecurityUtils.class); + + @Autowired(required=true) IConfiguration authConfig; + private Key signPrivKey = null; + private X509Certificate[] signCertChain = null; + + private Key encPrivKey = null; + private X509Certificate[] encCertChain = null; + + private List trustedCerts = new ArrayList(); + + private static JsonMapper mapper = new JsonMapper(); + + @PostConstruct + protected void initalize() { + log.info("Initialize SL2.0 authentication security constrains ... "); + try { + if (getKeyStoreFilePath() != null) { + KeyStore keyStore = KeyStoreUtils.loadKeyStore(getKeyStoreFilePath(), + getKeyStorePassword()); + + //load signing key + signPrivKey = keyStore.getKey(getSigningKeyAlias(), getSigningKeyPassword().toCharArray()); + Certificate[] certChainSigning = keyStore.getCertificateChain(getSigningKeyAlias()); + signCertChain = new X509Certificate[certChainSigning.length]; + for (int i=0; i aliases = keyStore.aliases(); + while(aliases.hasMoreElements()) { + String el = aliases.nextElement(); + log.trace("Process TrustStoreEntry: " + el); + if (keyStore.isCertificateEntry(el)) { + Certificate cert = keyStore.getCertificate(el); + if (cert != null && cert instanceof X509Certificate) + trustedCerts.add((X509Certificate) cert); + else + log.info("Can not process entry: " + el + ". Reason: " + cert.toString()); + + } + } + + //some short validation + if (signPrivKey == null || !(signPrivKey instanceof PrivateKey)) { + log.info("Can NOT open privateKey for SL2.0 signing. KeyStore=" + getKeyStoreFilePath()); + throw new SL20Exception("sl20.03", new Object[]{"Can NOT open private key for signing"}); + + } + + if (signCertChain == null || signCertChain.length == 0) { + log.info("NO certificate for SL2.0 signing. KeyStore=" + getKeyStoreFilePath()); + throw new SL20Exception("sl20.03", new Object[]{"NO certificate for SL2.0 signing"}); + + } + + log.info("SL2.0 authentication security constrains initialized."); + + } else + log.info("NO SL2.0 authentication security configuration. Initialization was skipped"); + + } catch ( Exception e) { + log.error("SL2.0 security constrains initialization FAILED.", e); + + } + + } + + + @Override + public String createSignature(String payLoad) throws SLCommandoBuildException { + try { + JsonWebSignature jws = new JsonWebSignature(); + + //set payload + jws.setPayload(payLoad); + + //set basic header + jws.setContentTypeHeaderValue(SL20Constants.SL20_CONTENTTYPE_SIGNED_COMMAND); + + //set signing information + jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); + jws.setKey(signPrivKey); + + //TODO: + jws.setCertificateChainHeaderValue(signCertChain); + jws.setX509CertSha256ThumbprintHeaderValue(signCertChain[0]); + + return jws.getCompactSerialization(); + + } catch (JoseException e) { + log.warn("Can NOT sign SL2.0 command.", e); + throw new SLCommandoBuildException("Can NOT sign SL2.0 command.", e); + + } + + } + + @Override + public VerificationResult validateSignature(String serializedContent) throws SL20Exception { + try { + JsonWebSignature jws = new JsonWebSignature(); + //set payload + jws.setCompactSerialization(serializedContent); + + //set security constrains + jws.setAlgorithmConstraints(new AlgorithmConstraints(ConstraintType.WHITELIST, + SL20Constants.SL20_ALGORITHM_WHITELIST_SIGNING.toArray(new String[SL20Constants.SL20_ALGORITHM_WHITELIST_SIGNING.size()]))); + + //load signinc certs + Key selectedKey = null; + List x5cCerts = jws.getCertificateChainHeaderValue(); + String x5t256 = jws.getX509CertSha256ThumbprintHeaderValue(); + if (x5cCerts != null) { + log.debug("Found x509 certificate in JOSE header ... "); + log.trace("Sorting received X509 certificates ... "); + List 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."); + log.debug("JOSE certificate: " + sortedX5cCerts.get(0).toString()); + try { + log.debug("Cert: " + Base64Utils.encodeToString(sortedX5cCerts.get(0).getEncoded())); + } catch (CertificateEncodingException e) { + e.printStackTrace(); + } + + } + + } else if (StringUtils.isNotEmpty(x5t256)) { + log.debug("Found x5t256 fingerprint in JOSE header .... "); + X509VerificationKeyResolver x509VerificationKeyResolver = new X509VerificationKeyResolver(trustedCerts); + selectedKey = x509VerificationKeyResolver.resolveKey(jws, Collections.emptyList()); + + } else { + log.info("Signed SL2.0 response contains NO signature certificate or NO certificate fingerprint"); + throw new SLCommandoParserException("Signed SL2.0 response contains NO signature certificate or NO certificate fingerprint"); + + } + + if (selectedKey == null) { + log.info("Can NOT select verification key for JWS. Signature verification FAILED."); + throw new SLCommandoParserException("Can NOT select verification key for JWS. Signature verification FAILED"); + + } + + //set verification key + jws.setKey(selectedKey); + + //validate signature + boolean valid = jws.verifySignature(); + if (!valid) { + log.info("JWS signature invalide. Stopping authentication process ..."); + log.debug("Received JWS msg: " + serializedContent); + throw new SL20SecurityException("JWS signature invalide."); + + } + + + //load payLoad + log.debug("SL2.0 commando signature validation sucessfull"); + JsonNode sl20Req = mapper.getMapper().readTree(jws.getPayload()); + + return new VerificationResult(sl20Req, null, valid) ; + + } catch (JoseException | JsonParseException e) { + log.warn("SL2.0 commando signature validation FAILED", e); + throw new SL20SecurityException(new Object[]{e.getMessage()}, e); + + } catch (IOException e) { + log.warn("Decrypted SL2.0 result can not be parsed.", e); + throw new SLCommandoParserException("Decrypted SL2.0 result can not be parsed", e); + + } + + } + + + @Override + public JsonNode decryptPayload(String compactSerialization) throws SL20Exception { + try { + JsonWebEncryption receiverJwe = new JsonWebEncryption(); + + //set security constrains + receiverJwe.setAlgorithmConstraints( + new AlgorithmConstraints(ConstraintType.WHITELIST, + SL20Constants.SL20_ALGORITHM_WHITELIST_KEYENCRYPTION.toArray(new String[SL20Constants.SL20_ALGORITHM_WHITELIST_KEYENCRYPTION.size()]))); + receiverJwe.setContentEncryptionAlgorithmConstraints( + new AlgorithmConstraints(ConstraintType.WHITELIST, + SL20Constants.SL20_ALGORITHM_WHITELIST_ENCRYPTION.toArray(new String[SL20Constants.SL20_ALGORITHM_WHITELIST_ENCRYPTION.size()]))); + + //set payload + receiverJwe.setCompactSerialization(compactSerialization); + + + //validate key from header against key from config + List x5cCerts = receiverJwe.getCertificateChainHeaderValue(); + String x5t256 = receiverJwe.getX509CertSha256ThumbprintHeaderValue(); + if (x5cCerts != null) { + log.debug("Found x509 certificate in JOSE header ... "); + log.trace("Sorting received X509 certificates ... "); + List sortedX5cCerts = X509Utils.sortCertificates(x5cCerts); + + if (!sortedX5cCerts.get(0).equals(encCertChain[0])) { + log.info("Certificate from JOSE header does NOT match encryption certificate"); + log.debug("JOSE certificate: " + sortedX5cCerts.get(0).toString()); + + try { + log.debug("Cert: " + Base64Utils.encode(sortedX5cCerts.get(0).getEncoded())); + } catch (CertificateEncodingException e) { + e.printStackTrace(); + } + throw new SL20Exception("sl20.05", new Object[]{"Certificate from JOSE header does NOT match encryption certificate"}); + } + + } else if (StringUtils.isNotEmpty(x5t256)) { + log.debug("Found x5t256 fingerprint in JOSE header .... "); + String certFingerPrint = X509Util.x5tS256(encCertChain[0]); + if (!certFingerPrint.equals(x5t256)) { + log.info("X5t256 from JOSE header does NOT match encryption certificate"); + log.debug("X5t256 from JOSE header: " + x5t256 + " Encrytption cert: " + certFingerPrint); + throw new SL20Exception("sl20.05", new Object[]{"X5t256 from JOSE header does NOT match encryption certificate"}); + + } + + } else { + log.info("Signed SL2.0 response contains NO signature certificate or NO certificate fingerprint"); + throw new SLCommandoParserException("Signed SL2.0 response contains NO signature certificate or NO certificate fingerprint"); + + } + + //set key + receiverJwe.setKey(encPrivKey); + + + //decrypt payload + return mapper.getMapper().readTree(receiverJwe.getPlaintextString()); + + } catch (JoseException e) { + log.warn("SL2.0 result decryption FAILED", e); + throw new SL20SecurityException(new Object[]{e.getMessage()}, e); + + } catch ( JsonParseException e) { + log.warn("Decrypted SL2.0 result is NOT a valid JSON.", e); + throw new SLCommandoParserException("Decrypted SL2.0 result is NOT a valid JSON.", e); + + } catch (IOException e) { + log.warn("Decrypted SL2.0 result can not be parsed.", e); + throw new SLCommandoParserException("Decrypted SL2.0 result can not be parsed", e); + } + + } + + + + @Override + public X509Certificate getEncryptionCertificate() { + //TODO: maybe update after SL2.0 update on encryption certificate parts + if (encCertChain !=null && encCertChain.length > 0) + return encCertChain[0]; + else + return null; + } + + private String getKeyStoreFilePath() throws EAAFConfigurationException, MalformedURLException { + return FileUtils.makeAbsoluteURL( + authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_PATH), + authConfig.getConfigurationRootDirectory()); + } + + private String getKeyStorePassword() { + String value = authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_PASSWORD); + if (value != null) + value = value.trim(); + + return value; + + } + + private String getSigningKeyAlias() { + String value = authConfig.getBasicConfiguration( + Constants.CONFIG_PROP_SECURITY_KEYSTORE_KEY_SIGN_ALIAS).trim(); + if (value != null) + value = value.trim(); + + return value; + } + + private String getSigningKeyPassword() { + String value = authConfig.getBasicConfiguration( + Constants.CONFIG_PROP_SECURITY_KEYSTORE_KEY_SIGN_PASSWORD).trim(); + if (value != null) + value = value.trim(); + + return value; + } + + private String getEncryptionKeyAlias() { + String value = authConfig.getBasicConfiguration( + Constants.CONFIG_PROP_SECURITY_KEYSTORE_KEY_ENCRYPTION_ALIAS).trim(); + if (value != null) + value = value.trim(); + + return value; + } + + private String getEncryptionKeyPassword() { + String value = authConfig.getBasicConfiguration( + Constants.CONFIG_PROP_SECURITY_KEYSTORE_KEY_ENCRYPTION_PASSWORD).trim(); + if (value != null) + value = value.trim(); + + return value; + } + +} -- cgit v1.2.3