diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/pom.xml b/dsf-bpe/dsf-bpe-process-api-v2-impl/pom.xml index 9b1c4cf9e..a2f40486f 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/pom.xml +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/pom.xml @@ -57,6 +57,12 @@ com.auth0 java-jwt + + + de.hs-heilbronn.mi + crypto-utils + ${crypto-utils.version.v2} + org.apache.logging.log4j @@ -93,6 +99,11 @@ dev.dsf dsf-bpe-process-api-v2-impl + + de.hs-heilbronn.mi + crypto-utils + ${crypto-utils.version.v2} + ca.uhn.hapi.fhir hapi-fhir-structures-r4 diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/ProcessPluginApiImpl.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/ProcessPluginApiImpl.java index ac12106dc..78ab08bde 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/ProcessPluginApiImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/ProcessPluginApiImpl.java @@ -8,6 +8,7 @@ import ca.uhn.fhir.context.FhirContext; import dev.dsf.bpe.v2.config.ProxyConfig; +import dev.dsf.bpe.v2.service.CryptoService; import dev.dsf.bpe.v2.service.DsfClientProvider; import dev.dsf.bpe.v2.service.EndpointProvider; import dev.dsf.bpe.v2.service.FhirClientProvider; @@ -34,13 +35,14 @@ public class ProcessPluginApiImpl implements ProcessPluginApi, InitializingBean private final QuestionnaireResponseHelper questionnaireResponseHelper; private final ReadAccessHelper readAccessHelper; private final TaskHelper taskHelper; + private final CryptoService cryptoService; public ProcessPluginApiImpl(ProxyConfig proxyConfig, EndpointProvider endpointProvider, FhirContext fhirContext, DsfClientProvider dsfClientProvider, FhirClientProvider fhirClientProvider, OidcClientProvider oidcClientProvider, MailService mailService, ObjectMapper objectMapper, OrganizationProvider organizationProvider, ProcessAuthorizationHelper processAuthorizationHelper, QuestionnaireResponseHelper questionnaireResponseHelper, ReadAccessHelper readAccessHelper, - TaskHelper taskHelper) + TaskHelper taskHelper, CryptoService cryptoService) { this.proxyConfig = proxyConfig; this.endpointProvider = endpointProvider; @@ -55,6 +57,7 @@ public ProcessPluginApiImpl(ProxyConfig proxyConfig, EndpointProvider endpointPr this.questionnaireResponseHelper = questionnaireResponseHelper; this.readAccessHelper = readAccessHelper; this.taskHelper = taskHelper; + this.cryptoService = cryptoService; } @Override @@ -73,6 +76,7 @@ public void afterPropertiesSet() throws Exception Objects.requireNonNull(questionnaireResponseHelper, "questionnaireResponseHelper"); Objects.requireNonNull(readAccessHelper, "readAccessHelper"); Objects.requireNonNull(taskHelper, "taskHelper"); + Objects.requireNonNull(cryptoService, "cryptoService"); } @Override @@ -152,4 +156,10 @@ public TaskHelper getTaskHelper() { return taskHelper; } + + @Override + public CryptoService getCryptoService() + { + return cryptoService; + } } diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/CryptoServiceImpl.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/CryptoServiceImpl.java new file mode 100644 index 000000000..6539d716a --- /dev/null +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/CryptoServiceImpl.java @@ -0,0 +1,217 @@ +package dev.dsf.bpe.v2.service; + +import java.io.IOException; +import java.io.InputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyManagementException; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import javax.crypto.DecapsulateException; +import javax.crypto.NoSuchPaddingException; +import javax.net.ssl.SSLContext; + +import de.hsheilbronn.mi.utils.crypto.cert.CertificateValidator; +import de.hsheilbronn.mi.utils.crypto.context.SSLContextFactory; +import de.hsheilbronn.mi.utils.crypto.io.KeyStoreReader; +import de.hsheilbronn.mi.utils.crypto.io.PemReader; +import de.hsheilbronn.mi.utils.crypto.kem.AbstractKemAesGcm; +import de.hsheilbronn.mi.utils.crypto.kem.EcDhKemAesGcm; +import de.hsheilbronn.mi.utils.crypto.kem.RsaKemAesGcm; +import de.hsheilbronn.mi.utils.crypto.keypair.KeyPairGeneratorFactory; +import de.hsheilbronn.mi.utils.crypto.keypair.KeyPairValidator; +import de.hsheilbronn.mi.utils.crypto.keystore.KeyStoreCreator; + +public class CryptoServiceImpl implements CryptoService +{ + public static final class KemDelegate implements Kem + { + private final AbstractKemAesGcm delegate; + + public KemDelegate(AbstractKemAesGcm delegate) + { + this.delegate = delegate; + } + + @Override + public InputStream encrypt(InputStream data, PublicKey publicKey) throws NoSuchAlgorithmException, + InvalidKeyException, NoSuchPaddingException, InvalidAlgorithmParameterException + { + return delegate.encrypt(data, publicKey); + } + + @Override + public InputStream decrypt(InputStream encrypted, PrivateKey privateKey) + throws IOException, NoSuchAlgorithmException, InvalidKeyException, DecapsulateException, + NoSuchPaddingException, InvalidAlgorithmParameterException + { + return delegate.decrypt(encrypted, privateKey); + } + } + + @Override + public Kem createRsaKem() + { + return new KemDelegate(new RsaKemAesGcm()); + } + + @Override + public Kem createEcDhKem() + { + return new KemDelegate(new EcDhKemAesGcm()); + } + + @Override + public KeyPairGenerator createKeyPairGeneratorRsa4096AndInitialize() + { + return KeyPairGeneratorFactory.rsa4096().initialize(); + } + + @Override + public KeyPairGenerator createKeyPairGeneratorSecp256r1AndInitialize() + { + return KeyPairGeneratorFactory.secp256r1().initialize(); + } + + @Override + public KeyPairGenerator createKeyPairGeneratorSecp384r1AndInitialize() + { + return KeyPairGeneratorFactory.secp384r1().initialize(); + } + + @Override + public KeyPairGenerator createKeyPairGeneratorSecp521r1AndInitialize() + { + return KeyPairGeneratorFactory.secp521r1().initialize(); + } + + @Override + public KeyPairGenerator createKeyPairGeneratorX25519AndInitialize() + { + return KeyPairGeneratorFactory.x25519().initialize(); + } + + @Override + public KeyPairGenerator createKeyPairGeneratorX448AndInitialize() + { + return KeyPairGeneratorFactory.x448().initialize(); + } + + @Override + public X509Certificate readCertificate(InputStream pem) throws IOException + { + return PemReader.readCertificate(pem); + } + + @Override + public List readCertificates(InputStream pem) throws IOException + { + return PemReader.readCertificates(pem); + } + + @Override + public PrivateKey readPrivateKey(InputStream pem, char[] password) throws IOException + { + return PemReader.readPrivateKey(pem, password); + } + + @Override + public boolean isKeyPair(PrivateKey privateKey, PublicKey publicKey) + { + return KeyPairValidator.matches(privateKey, publicKey); + } + + @Override + public boolean isCertificateExpired(X509Certificate certificate) + { + return CertificateValidator.isCertificateExpired(certificate); + } + + @Override + public boolean isClientCertificate(X509Certificate certificate) + { + return CertificateValidator.isClientCertificate(certificate); + } + + @Override + public boolean isServerCertificate(X509Certificate certificate) + { + return CertificateValidator.isServerCertificate(certificate); + } + + @Override + public void validateClientCertificate(KeyStore trustStore, Collection certificateChain) + throws CertificateException + { + Objects.requireNonNull(trustStore, "trustStore"); + Objects.requireNonNull(certificateChain, "certificateChain"); + + CertificateValidator.vaildateClientCertificate(trustStore, certificateChain); + } + + @Override + public void validateServerCertificate(KeyStore trustStore, Collection certificateChain) + throws CertificateException + { + Objects.requireNonNull(trustStore, "trustStore"); + Objects.requireNonNull(certificateChain, "certificateChain"); + + CertificateValidator.vaildateServerCertificate(trustStore, certificateChain); + } + + @Override + public KeyStore createKeyStoreForPrivateKeyAndCertificateChain(PrivateKey key, char[] password, + Collection chain) + { + return KeyStoreCreator.jksForPrivateKeyAndCertificateChain(key, password, chain); + } + + @Override + public KeyStore createKeyStoreForTrustedCertificates(Collection certificates) + { + return KeyStoreCreator.jksForTrustedCertificates(certificates); + } + + @Override + public KeyStore readKeyStoreJks(InputStream stream, char[] password) throws IOException + { + return KeyStoreReader.readJks(stream, password); + } + + @Override + public KeyStore readKeyStorePkcs12(InputStream stream, char[] password) throws IOException + { + return KeyStoreReader.readPkcs12(stream, password); + } + + @Override + public SSLContext createSSLContext(KeyStore trustStore) + throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException + { + Objects.requireNonNull(trustStore, "trustStore"); + + return SSLContextFactory.createSSLContext(trustStore); + } + + @Override + public SSLContext createSSLContext(KeyStore trustStore, KeyStore keyStore, char[] keyStorePassword) + throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException + { + Objects.requireNonNull(trustStore, "trustStore"); + Objects.requireNonNull(keyStore, "keyStore"); + Objects.requireNonNull(keyStorePassword, "keyStorePassword"); + + return SSLContextFactory.createSSLContext(trustStore, keyStore, keyStorePassword); + } +} diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/spring/ApiServiceConfig.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/spring/ApiServiceConfig.java index 012e26dd7..7921ee466 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/spring/ApiServiceConfig.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/spring/ApiServiceConfig.java @@ -33,6 +33,8 @@ import dev.dsf.bpe.v2.listener.EndListener; import dev.dsf.bpe.v2.listener.StartListener; import dev.dsf.bpe.v2.plugin.ProcessPluginFactoryImpl; +import dev.dsf.bpe.v2.service.CryptoService; +import dev.dsf.bpe.v2.service.CryptoServiceImpl; import dev.dsf.bpe.v2.service.DsfClientProvider; import dev.dsf.bpe.v2.service.DsfClientProviderImpl; import dev.dsf.bpe.v2.service.EndpointProvider; @@ -88,7 +90,8 @@ public ProcessPluginApi processPluginApiV2() { return new ProcessPluginApiImpl(proxyConfigDelegate(), endpointProvider(), fhirContext(), dsfClientProvider(), fhirClientProvider(), oidcClientProvider(), mailService(), objectMapper(), organizationProvider(), - processAuthorizationHelper(), questionnaireResponseHelper(), readAccessHelper(), taskHelper()); + processAuthorizationHelper(), questionnaireResponseHelper(), readAccessHelper(), taskHelper(), + cryptoService()); } @Bean @@ -255,4 +258,10 @@ public ListenerFactory listenerFactory() return new ListenerFactoryImpl(ProcessPluginFactoryImpl.API_VERSION, startListener(), endListener(), continueListener()); } + + @Bean + public CryptoService cryptoService() + { + return new CryptoServiceImpl(); + } } diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/ProcessPluginApi.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/ProcessPluginApi.java index 76ec1d614..f8689bdc6 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/ProcessPluginApi.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/ProcessPluginApi.java @@ -7,6 +7,7 @@ import ca.uhn.fhir.context.FhirContext; import dev.dsf.bpe.v2.config.ProxyConfig; +import dev.dsf.bpe.v2.service.CryptoService; import dev.dsf.bpe.v2.service.DsfClientProvider; import dev.dsf.bpe.v2.service.EndpointProvider; import dev.dsf.bpe.v2.service.FhirClientProvider; @@ -52,4 +53,6 @@ public interface ProcessPluginApi ReadAccessHelper getReadAccessHelper(); TaskHelper getTaskHelper(); + + CryptoService getCryptoService(); } diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/CryptoService.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/CryptoService.java new file mode 100644 index 000000000..c9e618c81 --- /dev/null +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/CryptoService.java @@ -0,0 +1,518 @@ +package dev.dsf.bpe.v2.service; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyManagementException; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateCrtKey; +import java.security.interfaces.RSAPublicKey; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import javax.crypto.DecapsulateException; +import javax.crypto.NoSuchPaddingException; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +/** + * Provides methods for: + *
    + *
  • Creating and using RSA and ECDH key encapsulation mechanism
  • + *
  • Reading X509 certificates and private-keys (encrypted or not encrypted)
  • + *
  • Reading JKS and PKCS12 key-stores
  • + *
  • Creating JKS and PKCS12 key-stores based on trusted certificates or private-key and certificate chain
  • + *
  • Generating RSA (4096 bit), EC (secp256r1, secp384r1, secp521r1, X25519, X448) key-pairs
  • + *
  • Validating key-pairs to check if a private-key belongs to a public-key
  • + *
  • Validating certificates
  • + *
  • Creating {@link SSLContext}s based on a key-store with trusted certificates and/or a key-store with private-key + * and certificate chain
  • + *
+ */ +public interface CryptoService +{ + /** + * Key encapsulation mechanism with encrypt and decrypt methods. + */ + public interface Kem + { + /** + * Encrypts the given {@link InputStream} with an AES session key calculated by KEM for the given + * {@link PublicKey}. The returned {@link InputStream} has the form [encapsulation length (big-endian, 2 bytes), + * encapsulation, AES initialization vector (12 bytes), AES encrypted data]. + * + * @param data + * not null + * @param publicKey + * not null + * @return byte array of [encapsulation length (big-endian, 2 bytes), encapsulation, iv (12 bytes), encrypted + * data] + * @throws IOException + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + * @throws NoSuchPaddingException + * @throws InvalidAlgorithmParameterException + */ + default byte[] encrypt(byte[] data, PublicKey publicKey) throws IOException, NoSuchAlgorithmException, + InvalidKeyException, NoSuchPaddingException, InvalidAlgorithmParameterException + { + return encrypt(new ByteArrayInputStream(data), publicKey).readAllBytes(); + } + + /** + * Encrypts the given {@link InputStream} with an AES session key calculated by KEM for the given + * {@link PublicKey}. The returned {@link InputStream} has the form [encapsulation length (big-endian, 2 bytes), + * encapsulation, AES initialization vector (12 bytes), AES encrypted data]. + * + * @param data + * not null + * @param publicKey + * not null + * @return {@link InputStream} of [encapsulation length (big-endian, 2 bytes), encapsulation, iv (12 bytes), + * encrypted data] + * @throws IOException + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + * @throws NoSuchPaddingException + * @throws InvalidAlgorithmParameterException + */ + InputStream encrypt(InputStream data, PublicKey publicKey) throws IOException, NoSuchAlgorithmException, + InvalidKeyException, NoSuchPaddingException, InvalidAlgorithmParameterException; + + /** + * @param encrypted + * not null, {@link InputStream} of [encapsulation length (big-endian, 2 bytes), + * encapsulation, iv (12 bytes), encrypted data] + * @param privateKey + * not null + * @return decrypted data + * @throws IOException + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + * @throws DecapsulateException + * @throws NoSuchPaddingException + * @throws InvalidAlgorithmParameterException + */ + default byte[] decrypt(byte[] encrypted, PrivateKey privateKey) throws IOException, NoSuchAlgorithmException, + InvalidKeyException, DecapsulateException, NoSuchPaddingException, InvalidAlgorithmParameterException + { + return decrypt(new ByteArrayInputStream(encrypted), privateKey).readAllBytes(); + } + + /** + * @param encrypted + * not null, {@link InputStream} of [encapsulation length (big-endian, 2 bytes), + * encapsulation, iv (12 bytes), encrypted data] + * @param privateKey + * not null + * @return decrypted data + * @throws IOException + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + * @throws DecapsulateException + * @throws NoSuchPaddingException + * @throws InvalidAlgorithmParameterException + */ + InputStream decrypt(InputStream encrypted, PrivateKey privateKey) throws IOException, NoSuchAlgorithmException, + InvalidKeyException, DecapsulateException, NoSuchPaddingException, InvalidAlgorithmParameterException; + } + + /** + * @return key encapsulation mechanism with RSA key exchange using KDF2 SHA-512 for AES-256, use with RSA key pairs + */ + Kem createRsaKem(); + + /** + * @return key encapsulation mechanism with Diffie–Hellman key exchange for AES-256, use with elliptic curve key + * pairs like X25519, X448, secp256r1, secp384r1 and secp521r1 + */ + Kem createEcDhKem(); + + /** + * @return created and initialized RSA (4096 bit) key pair generator + * @see KeyPairGenerator#generateKeyPair() + */ + KeyPairGenerator createKeyPairGeneratorRsa4096AndInitialize(); + + /** + * @return created and initialized secp256r1 key pair generator + * @see KeyPairGenerator#generateKeyPair() + */ + KeyPairGenerator createKeyPairGeneratorSecp256r1AndInitialize(); + + /** + * @return created and initialized secp384r1 key pair generator + * @see KeyPairGenerator#generateKeyPair() + */ + KeyPairGenerator createKeyPairGeneratorSecp384r1AndInitialize(); + + /** + * @return created and initialized secp521r1 key pair generator + * @see KeyPairGenerator#generateKeyPair() + */ + KeyPairGenerator createKeyPairGeneratorSecp521r1AndInitialize(); + + /** + * @return created and initialized x25519 key pair generator + * @see KeyPairGenerator#generateKeyPair() + */ + KeyPairGenerator createKeyPairGeneratorX25519AndInitialize(); + + /** + * @return created and initialized x448 key pair generator + * @see KeyPairGenerator#generateKeyPair() + */ + KeyPairGenerator createKeyPairGeneratorX448AndInitialize(); + + /** + * @param pem + * not null + * @return certificate + * @throws IOException + * if the given file does not contain a pem encoded certificate, more than one or is not readable or + * parsable + */ + default X509Certificate readCertificate(Path pem) throws IOException + { + Objects.requireNonNull(pem, "pem"); + + try (InputStream in = Files.newInputStream(pem)) + { + return readCertificate(in); + } + } + + /** + * @param pem + * not null + * @return certificate + * @throws IOException + * if the given {@link InputStream} does not contain a pem encoded certificate, more than one or is not + * readable or parsable + */ + X509Certificate readCertificate(InputStream pem) throws IOException; + + /** + * @param pem + * not null + * @return list of certificates + * @throws IOException + * if the given file does not contain pem encoded certificates or is not readable or one is not parsable + */ + default List readCertificates(Path pem) throws IOException + { + Objects.requireNonNull(pem, "pem"); + + try (InputStream in = Files.newInputStream(pem)) + { + return readCertificates(in); + } + } + + /** + * @param pem + * @return list of certificates + * @throws IOException + * if the given {@link InputStream} does not contain pem encoded certificates or is not readable or one + * is not parsable + */ + List readCertificates(InputStream pem) throws IOException; + + /** + * @param pem + * not null + * @return private key + * @throws IOException + * if the given file does not contain a pem encoded, unencrypted private key, more than one or is not + * readable or parsable + */ + default PrivateKey readPrivateKey(Path pem) throws IOException + { + return readPrivateKey(pem, null); + } + + /** + * @param pem + * not null + * @return private key + * @throws IOException + * if the given {@link InputStream} does not contain a pem encoded, unencrypted private key, more than + * one or is not readable or parsable + */ + default PrivateKey readPrivateKey(InputStream pem) throws IOException + { + return readPrivateKey(pem, null); + } + + /** + * @param pem + * not null + * @param password + * if key encrypted not null + * @return private key + * @throws IOException + * if the given file does not contain a pem encoded private key, more than one or is not readable or + * parsable + */ + default PrivateKey readPrivateKey(Path pem, char[] password) throws IOException + { + Objects.requireNonNull(pem, "pem"); + + try (InputStream in = Files.newInputStream(pem)) + { + return readPrivateKey(in, password); + } + } + + /** + * @param pem + * not null + * @param password + * if key encrypted not null + * @return private key + * @throws IOException + * if the given {@link InputStream} does not contain a pem encoded private key, more than one or is not + * readable or parsable + */ + PrivateKey readPrivateKey(InputStream pem, char[] password) throws IOException; + + /** + * Checks if the given privateKey and publicKey match by checking if a generated signature can be + * verified for RSA, EC and EdDSA key pairs or a Diffie-Hellman key agreement produces the same secret key for a XDH + * key pair. If the privateKey is a {@link RSAPrivateCrtKey} and the publicKey is a + * {@link RSAPublicKey} modulus and public-exponent will be compared. + * + * @param privateKey + * may be null + * @param publicKey + * may be null + * @return true if the given keys are not null and match + */ + boolean isKeyPair(PrivateKey privateKey, PublicKey publicKey); + + /** + * @param certificate + * not null + * @return true if the given certificate not-after field is after {@link ZonedDateTime#now()} + */ + boolean isCertificateExpired(X509Certificate certificate); + + /** + * @param certificate + * not null + * @return true if given certificate has extended key usage extension "TLS Web Client + * Authentication" + */ + boolean isClientCertificate(X509Certificate certificate); + + /** + * @param certificate + * not null + * @return true if given certificate has extended key usage extension "TLS Web Server + * Authentication" + */ + boolean isServerCertificate(X509Certificate certificate); + + /** + * @param trustStore + * not null + * @param certificateChain + * not null + * @throws CertificateException + * if the the given certificate or certificate chain is not trusted as a client certificate by a PKIX + * trust manager created for the given trust store + */ + default void validateClientCertificate(KeyStore trustStore, X509Certificate... certificateChain) + throws CertificateException + { + validateClientCertificate(trustStore, List.of(certificateChain)); + } + + /** + * @param trustStore + * not null + * @param certificateChain + * not null + * @throws CertificateException + * if the the given certificate or certificate chain is not trusted as a client certificate by a PKIX + * trust manager created for the given trust store + */ + void validateClientCertificate(KeyStore trustStore, Collection certificateChain) + throws CertificateException; + + /** + * @param trustStore + * not null + * @param certificateChain + * not null + * @throws CertificateException + * if the the given certificate or certificate chain is not trusted as a server certificate by a PKIX + * trust manager created for the given trust store + */ + default void validateServerCertificate(KeyStore trustStore, X509Certificate... certificateChain) + throws CertificateException + { + validateServerCertificate(trustStore, List.of(certificateChain)); + } + + /** + * @param trustStore + * not null + * @param certificateChain + * not null + * @throws CertificateException + * if the the given certificate or certificate chain is not trusted as a server certificate by a PKIX + * trust manager created for the given trust store + */ + void validateServerCertificate(KeyStore trustStore, Collection certificateChain) + throws CertificateException; + + /** + * @param key + * not null + * @param password + * not null + * @param chain + * not null, at least one + * @return jks {@link KeyStore} for the given key and chain + */ + default KeyStore createKeyStoreForPrivateKeyAndCertificateChain(PrivateKey key, char[] password, + X509Certificate... chain) + { + return createKeyStoreForPrivateKeyAndCertificateChain(key, password, Arrays.asList(chain)); + } + + /** + * @param key + * not null + * @param password + * not null + * @param chain + * not null, at least one + * @return jks {@link KeyStore} for the given key and chain + */ + KeyStore createKeyStoreForPrivateKeyAndCertificateChain(PrivateKey key, char[] password, + Collection chain); + + /** + * @param certificates + * not null, at least one + * @return jks {@link KeyStore} for the given certificates + */ + default KeyStore createKeyStoreForTrustedCertificates(X509Certificate... certificates) + { + return createKeyStoreForTrustedCertificates(List.of(certificates)); + } + + /** + * @param certificates + * not null, at least one + * @return jks {@link KeyStore} for the given certificates + */ + KeyStore createKeyStoreForTrustedCertificates(Collection certificates); + + /** + * @param file + * not null + * @param password + * if not null used to check the integrity of the keystore + * @return jks {@link KeyStore} + * @throws IOException + * @see KeyStore#load(InputStream, char[]) + */ + default KeyStore readKeyStoreJks(Path file, char[] password) throws IOException + { + Objects.requireNonNull(file, "file"); + + try (InputStream in = Files.newInputStream(file)) + { + return readKeyStoreJks(in, password); + } + } + + /** + * @param stream + * not null + * @param password + * if not null used to check the integrity of the keystore + * @return jks {@link KeyStore} + * @throws IOException + * @see KeyStore#load(InputStream, char[]) + */ + KeyStore readKeyStoreJks(InputStream stream, char[] password) throws IOException; + + /** + * @param file + * not null + * @param password + * if not null used to check the integrity of the keystore + * @return pkcs12 {@link KeyStore} + * @throws IOException + * @see KeyStore#load(InputStream, char[]) + */ + default KeyStore readKeyStorePkcs12(Path file, char[] password) throws IOException + { + Objects.requireNonNull(file, "file"); + + try (InputStream in = Files.newInputStream(file)) + { + return readKeyStorePkcs12(in, password); + } + } + + /** + * @param stream + * not null + * @param password + * if not null used to check the integrity of the keystore + * @return pkcs12 {@link KeyStore} + * @throws IOException + * @see KeyStore#load(InputStream, char[]) + */ + KeyStore readKeyStorePkcs12(InputStream stream, char[] password) throws IOException; + + /** + * @param trustStore + * not null + * @return {@link SSLContext} with {@link TrustManager} for the given trustStore + * @throws KeyStoreException + * @throws NoSuchAlgorithmException + * @throws UnrecoverableKeyException + * @throws KeyManagementException + */ + SSLContext createSSLContext(KeyStore trustStore) + throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException; + + /** + * @param trustStore + * not null + * @param keyStore + * not null + * @param keyStorePassword + * not null + * @return {@link SSLContext} with {@link TrustManager} for the given trustStore and {@link KeyManager} for + * the given keyStore / keyStorePassword + * @throws KeyStoreException + * @throws NoSuchAlgorithmException + * @throws UnrecoverableKeyException + * @throws KeyManagementException + */ + SSLContext createSSLContext(KeyStore trustStore, KeyStore keyStore, char[] keyStorePassword) + throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException; +} diff --git a/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/api/v2/allowed-bpe-classes.list b/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/api/v2/allowed-bpe-classes.list index 03b73b653..904d411e3 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/api/v2/allowed-bpe-classes.list +++ b/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/api/v2/allowed-bpe-classes.list @@ -12,6 +12,7 @@ org.apache.commons.io org.apache.commons.lang3 org.apache.commons.text org.apache.http +org.bouncycastle org.camunda.bpm.engine.delegate org.camunda.bpm.engine.impl.bpmn.parser.FieldDeclaration org.camunda.bpm.engine.impl.el.FixedValue diff --git a/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/AbstractIntegrationTest.java b/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/AbstractIntegrationTest.java index e1b39334b..c6779a7da 100644 --- a/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/AbstractIntegrationTest.java +++ b/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/AbstractIntegrationTest.java @@ -160,6 +160,7 @@ public static void beforeClass() throws Exception Paths.get("src/main/resources/bpe/api/v1/allowed-bpe-classes.list")); allowedBpeClassesV1.add("dev.dsf.bpe.test.PluginTest"); allowedBpeClassesV1.add("dev.dsf.bpe.test.PluginTestExecutor"); + allowedBpeClassesV1.add("dev.dsf.bpe.test.PluginTestExecutor$RunnableWithException"); writeListFile(ALLOWED_BPE_CLASSES_LIST_FILE_V1, allowedBpeClassesV1); // allowed bpe classes override to enable access to classes from dsf-bpe-test-plugin module for v2 test plugins @@ -167,6 +168,7 @@ public static void beforeClass() throws Exception Paths.get("src/main/resources/bpe/api/v2/allowed-bpe-classes.list")); allowedBpeClassesV2.add("dev.dsf.bpe.test.PluginTest"); allowedBpeClassesV2.add("dev.dsf.bpe.test.PluginTestExecutor"); + allowedBpeClassesV2.add("dev.dsf.bpe.test.PluginTestExecutor$RunnableWithException"); writeListFile(ALLOWED_BPE_CLASSES_LIST_FILE_V2, allowedBpeClassesV2); bpeDefaultDataSource = createBpeDefaultDataSource(bpeLiquibaseRule.getHost(), diff --git a/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/PluginV2IntegrationTest.java b/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/PluginV2IntegrationTest.java index d88707a32..83d2adafc 100644 --- a/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/PluginV2IntegrationTest.java +++ b/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/PluginV2IntegrationTest.java @@ -1,8 +1,22 @@ package dev.dsf.bpe.integration; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.cert.X509Certificate; +import java.util.List; + import org.junit.BeforeClass; import org.junit.Test; +import de.hsheilbronn.mi.utils.crypto.ca.CertificateAuthority; +import de.hsheilbronn.mi.utils.crypto.ca.CertificationRequest; +import de.hsheilbronn.mi.utils.crypto.ca.CertificationRequest.CertificationRequestAndPrivateKey; +import de.hsheilbronn.mi.utils.crypto.io.KeyStoreWriter; +import de.hsheilbronn.mi.utils.crypto.io.PemWriter; +import de.hsheilbronn.mi.utils.crypto.keystore.KeyStoreCreator; + public class PluginV2IntegrationTest extends AbstractPluginIntegrationTest { private static final String PROCESS_VERSION = "2.0"; @@ -83,4 +97,93 @@ public void startJsonVariableTest() throws Exception { executePluginTest(createTestTask("JsonVariableTest")); } + + @Test + public void startCryptoServiceTest() throws Exception + { + List filesToDelete = null; + try + { + filesToDelete = createCaCerKeyFiles(); + executePluginTest(createTestTask("CryptoServiceTest")); + } + finally + { + if (filesToDelete != null) + filesToDelete.forEach(this::deleteFile); + } + } + + private List createCaCerKeyFiles() throws Exception + { + CertificateAuthority ca = CertificateAuthority + .builderSha384EcdsaSecp384r1("DE", null, null, "DSF", "Test", "Plugin V2 Integration Test CA").build(); + CertificationRequestAndPrivateKey clientReq = CertificationRequest + .builder(ca, "DE", null, null, "DSF", "Test", "client").generateKeyPair().build(); + CertificationRequestAndPrivateKey serverReq = CertificationRequest + .builder(ca, "DE", null, null, "DSF", "Test", "server").generateKeyPair().build(); + X509Certificate clientCert = ca.signClientCertificate(clientReq); + X509Certificate serverCert = ca.signServerCertificate(serverReq); + + char[] password = "password".toCharArray(); + + Path caCertFile = Paths.get("target/plugin_v2_ca.crt"); + PemWriter.writeCertificate(ca.getCertificate(), caCertFile); + + Path caTrustStoreJksFile = Paths.get("target/plugin_v2_ca.jks"); + KeyStoreWriter.write(KeyStoreCreator.jksForTrustedCertificates(ca.getCertificate()), password, + caTrustStoreJksFile); + + Path caTrustStoreP12File = Paths.get("target/plugin_v2_ca.p12"); + KeyStoreWriter.write(KeyStoreCreator.pkcs12ForTrustedCertificates(ca.getCertificate()), password, + caTrustStoreP12File); + + Path clientCertFile = Paths.get("target/plugin_v2_client.crt"); + PemWriter.writeCertificate(clientCert, clientCertFile); + + Path clientKeyFile = Paths.get("target/plugin_v2_client.key"); + PemWriter.writePrivateKey(clientReq.getPrivateKey()).asPkcs8().encryptedAes128(password).toFile(clientKeyFile); + + Path clientKeyStoreJksFile = Paths.get("target/plugin_v2_client.jks"); + KeyStoreWriter.write( + KeyStoreCreator.jksForPrivateKeyAndCertificateChain(clientReq.getPrivateKey(), password, clientCert), + password, clientKeyStoreJksFile); + + Path clientKeyStoreP12File = Paths.get("target/plugin_v2_client.p12"); + KeyStoreWriter.write( + KeyStoreCreator.pkcs12ForPrivateKeyAndCertificateChain(clientReq.getPrivateKey(), password, clientCert), + password, clientKeyStoreP12File); + + Path serverCertFile = Paths.get("target/plugin_v2_server.crt"); + PemWriter.writeCertificate(serverCert, serverCertFile); + + Path serverKeyFile = Paths.get("target/plugin_v2_server.key"); + PemWriter.writePrivateKey(serverReq.getPrivateKey()).asPkcs8().encryptedAes128(password).toFile(serverKeyFile); + + Path serverKeyStoreJksFile = Paths.get("target/plugin_v2_server.jks"); + KeyStoreWriter.write( + KeyStoreCreator.jksForPrivateKeyAndCertificateChain(serverReq.getPrivateKey(), password, serverCert), + password, serverKeyStoreJksFile); + + Path serverKeyStoreP12File = Paths.get("target/plugin_v2_server.p12"); + KeyStoreWriter.write( + KeyStoreCreator.pkcs12ForPrivateKeyAndCertificateChain(serverReq.getPrivateKey(), password, serverCert), + password, serverKeyStoreP12File); + + return List.of(caCertFile, caTrustStoreJksFile, caTrustStoreP12File, clientCertFile, clientKeyFile, + clientKeyStoreJksFile, clientKeyStoreP12File, serverCertFile, serverKeyFile, serverKeyStoreJksFile, + serverKeyStoreP12File); + } + + private void deleteFile(Path path) + { + try + { + Files.deleteIfExists(path); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } } \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/message/ContinueSendTestSend.java b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/message/ContinueSendTestSend.java index 8a4000dca..c3dca08ef 100644 --- a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/message/ContinueSendTestSend.java +++ b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/message/ContinueSendTestSend.java @@ -4,5 +4,5 @@ public class ContinueSendTestSend implements MessageIntermediateThrowEvent { - // default implementation; + // default implementation } diff --git a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/ApiTest.java b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/ApiTest.java index de2e193b9..a80b43184 100644 --- a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/ApiTest.java +++ b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/ApiTest.java @@ -94,4 +94,10 @@ public void apiGetTaskHelperNotNull(ProcessPluginApi api) throws Exception { expectNotNull(api.getTaskHelper()); } + + @PluginTest + public void apiGetCryptoService(ProcessPluginApi api) throws Exception + { + expectNotNull(api.getCryptoService()); + } } diff --git a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/CryptoServiceTest.java b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/CryptoServiceTest.java new file mode 100644 index 000000000..5c25748a3 --- /dev/null +++ b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/CryptoServiceTest.java @@ -0,0 +1,438 @@ +package dev.dsf.bpe.test.service; + +import static dev.dsf.bpe.test.PluginTestExecutor.expectException; +import static dev.dsf.bpe.test.PluginTestExecutor.expectFalse; +import static dev.dsf.bpe.test.PluginTestExecutor.expectNotNull; +import static dev.dsf.bpe.test.PluginTestExecutor.expectSame; +import static dev.dsf.bpe.test.PluginTestExecutor.expectTrue; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import javax.net.ssl.SSLContext; + +import dev.dsf.bpe.test.AbstractTest; +import dev.dsf.bpe.test.PluginTest; +import dev.dsf.bpe.v2.ProcessPluginApi; +import dev.dsf.bpe.v2.activity.ServiceTask; +import dev.dsf.bpe.v2.error.ErrorBoundaryEvent; +import dev.dsf.bpe.v2.service.CryptoService; +import dev.dsf.bpe.v2.service.CryptoService.Kem; +import dev.dsf.bpe.v2.variables.Variables; + +public class CryptoServiceTest extends AbstractTest implements ServiceTask +{ + // files created by dev.dsf.bpe.integration.PluginV2IntegrationTest + private static final Path CA_CERT_FILE = Paths.get("target/plugin_v2_ca.crt"); + private static final Path CA_TRUST_STORE_JKS_FILE = Paths.get("target/plugin_v2_ca.jks"); + private static final Path CA_TRUST_STORE_P12_FILE = Paths.get("target/plugin_v2_ca.p12"); + + private static final Path CLIENT_CERT_FILE = Paths.get("target/plugin_v2_client.crt"); + private static final Path CLIENT_KEY_FILE = Paths.get("target/plugin_v2_client.key"); + private static final Path CLIENT_KEY_STORE_JKS_FILE = Paths.get("target/plugin_v2_client.jks"); + private static final Path CLIENT_KEY_STORE_P12_FILE = Paths.get("target/plugin_v2_client.p12"); + + private static final Path SERVER_CERT_FILE = Paths.get("target/plugin_v2_server.crt"); + private static final Path SERVER_KEY_FILE = Paths.get("target/plugin_v2_server.key"); + private static final Path SERVER_KEY_STORE_JKS_FILE = Paths.get("target/plugin_v2_server.jks"); + private static final Path SERVER_KEY_STORE_P12_FILE = Paths.get("target/plugin_v2_server.p12"); + + private static final char[] PASSWORD = "password".toCharArray(); + + @Override + public void execute(ProcessPluginApi api, Variables variables) throws ErrorBoundaryEvent, Exception + { + executeTests(api, variables, api.getCryptoService()); + } + + @PluginTest + public void createEcDhKem(CryptoService cryptoService) throws Exception + { + expectNotNull(cryptoService.createEcDhKem()); + } + + @PluginTest + public void createEcDhKemCheckEncryptionDecryption(CryptoService cryptoService) throws Exception + { + Kem kem = cryptoService.createEcDhKem(); + + KeyPair keyPair = cryptoService.createKeyPairGeneratorX25519AndInitialize().generateKeyPair(); + byte[] plainData = "Hello World".getBytes(StandardCharsets.UTF_8); + + InputStream encrypted = kem.encrypt(new ByteArrayInputStream(plainData), keyPair.getPublic()); + expectNotNull(encrypted); + + byte[] encryptedData = encrypted.readAllBytes(); + expectNotNull(encryptedData); + expectTrue(encryptedData.length > 12 + 2 + 1 + 1); + + InputStream decryptedDataStream = kem.decrypt(new ByteArrayInputStream(encryptedData), keyPair.getPrivate()); + byte[] decryptedData = decryptedDataStream.readAllBytes(); + expectNotNull(decryptedData); + expectSame(plainData, decryptedData); + } + + @PluginTest + public void createEcDhKemCheckEncryptionDecryptionByteArray(CryptoService cryptoService) throws Exception + { + Kem kem = cryptoService.createEcDhKem(); + + KeyPair keyPair = cryptoService.createKeyPairGeneratorX25519AndInitialize().generateKeyPair(); + byte[] plainData = "Hello World".getBytes(StandardCharsets.UTF_8); + + byte[] encryptedData = kem.encrypt(plainData, keyPair.getPublic()); + expectNotNull(encryptedData); + expectTrue(encryptedData.length > 12 + 2 + 1 + 1); + + byte[] decryptedData = kem.decrypt(encryptedData, keyPair.getPrivate()); + expectNotNull(decryptedData); + expectSame(plainData, decryptedData); + } + + @PluginTest + public void createKeyPairGeneratorRsa4096AndInitialize(CryptoService cryptoService) throws Exception + { + expectNotNull(cryptoService.createKeyPairGeneratorRsa4096AndInitialize()); + } + + @PluginTest + public void createKeyPairGeneratorSecp256r1AndInitialize(CryptoService cryptoService) throws Exception + { + expectNotNull(cryptoService.createKeyPairGeneratorSecp256r1AndInitialize()); + } + + @PluginTest + public void createKeyPairGeneratorSecp384r1AndInitialize(CryptoService cryptoService) throws Exception + { + expectNotNull(cryptoService.createKeyPairGeneratorSecp384r1AndInitialize()); + } + + @PluginTest + public void createKeyPairGeneratorSecp521r1AndInitialize(CryptoService cryptoService) throws Exception + { + expectNotNull(cryptoService.createKeyPairGeneratorSecp521r1AndInitialize()); + } + + @PluginTest + public void createKeyPairGeneratorX25519AndInitialize(CryptoService cryptoService) throws Exception + { + expectNotNull(cryptoService.createKeyPairGeneratorX25519AndInitialize()); + } + + @PluginTest + public void createKeyPairGeneratorX448AndInitialize(CryptoService cryptoService) throws Exception + { + expectNotNull(cryptoService.createKeyPairGeneratorX448AndInitialize()); + } + + @PluginTest + public void createKeyStoreForPrivateKeyAndCertificateChainCollection(CryptoService cryptoService) throws Exception + { + PrivateKey key = cryptoService.readPrivateKey(CLIENT_KEY_FILE, PASSWORD); + X509Certificate cert = cryptoService.readCertificate(CLIENT_CERT_FILE); + KeyStore store = cryptoService.createKeyStoreForPrivateKeyAndCertificateChain(key, + UUID.randomUUID().toString().toCharArray(), List.of(cert)); + expectNotNull(store); + expectSame(1, Collections.list(store.aliases()).size()); + } + + @PluginTest + public void createKeyStoreForPrivateKeyAndCertificateChainVarargs(CryptoService cryptoService) throws Exception + { + PrivateKey key = cryptoService.readPrivateKey(SERVER_KEY_FILE, PASSWORD); + X509Certificate cert = cryptoService.readCertificate(SERVER_CERT_FILE); + KeyStore store = cryptoService.createKeyStoreForPrivateKeyAndCertificateChain(key, + UUID.randomUUID().toString().toCharArray(), cert); + expectNotNull(store); + expectSame(1, Collections.list(store.aliases()).size()); + } + + @PluginTest + public void createKeyStoreForTrustedCertificatesCollection(CryptoService cryptoService) throws Exception + { + X509Certificate cert = cryptoService.readCertificate(CA_CERT_FILE); + KeyStore store = cryptoService.createKeyStoreForTrustedCertificates(List.of(cert)); + expectNotNull(store); + expectSame(1, Collections.list(store.aliases()).size()); + } + + @PluginTest + public void createKeyStoreForTrustedCertificatesVarargs(CryptoService cryptoService) throws Exception + { + X509Certificate cert = cryptoService.readCertificate(CA_CERT_FILE); + KeyStore store = cryptoService.createKeyStoreForTrustedCertificates(cert); + expectNotNull(store); + expectSame(1, Collections.list(store.aliases()).size()); + } + + @PluginTest + public void createRsaKem(CryptoService cryptoService) throws Exception + { + expectNotNull(cryptoService.createRsaKem()); + } + + @PluginTest + public void createRsaKemCheckEncryptionDecryption(CryptoService cryptoService) throws Exception + { + Kem kem = cryptoService.createRsaKem(); + + KeyPair keyPair = cryptoService.createKeyPairGeneratorRsa4096AndInitialize().generateKeyPair(); + byte[] plainData = "Hello World".getBytes(StandardCharsets.UTF_8); + + InputStream encrypted = kem.encrypt(new ByteArrayInputStream(plainData), keyPair.getPublic()); + expectNotNull(encrypted); + + byte[] encryptedData = encrypted.readAllBytes(); + expectNotNull(encryptedData); + expectTrue(encryptedData.length > 12 + 2 + 1 + 1); + + InputStream decryptedDataStream = kem.decrypt(new ByteArrayInputStream(encryptedData), keyPair.getPrivate()); + byte[] decryptedData = decryptedDataStream.readAllBytes(); + + expectSame(plainData, decryptedData); + } + + @PluginTest + public void createSSLContextTrustStore(CryptoService cryptoService) throws Exception + { + X509Certificate cert = cryptoService.readCertificate(CA_CERT_FILE); + KeyStore store = cryptoService.createKeyStoreForTrustedCertificates(cert); + + SSLContext context = cryptoService.createSSLContext(store); + expectNotNull(context); + } + + @PluginTest + public void createSSLContextTrustStoreKeyStore(CryptoService cryptoService) throws Exception + { + X509Certificate caCert = cryptoService.readCertificate(CA_CERT_FILE); + KeyStore trustStore = cryptoService.createKeyStoreForTrustedCertificates(caCert); + + PrivateKey clientKey = cryptoService.readPrivateKey(CLIENT_KEY_FILE, PASSWORD); + X509Certificate clientCert = cryptoService.readCertificate(CLIENT_CERT_FILE); + char[] keyStorePassword = UUID.randomUUID().toString().toCharArray(); + KeyStore keyStore = cryptoService.createKeyStoreForPrivateKeyAndCertificateChain(clientKey, keyStorePassword, + List.of(clientCert)); + + SSLContext context = cryptoService.createSSLContext(trustStore, keyStore, keyStorePassword); + expectNotNull(context); + } + + @PluginTest + public void isCertificateExpired(CryptoService cryptoService) throws Exception + { + X509Certificate caCert = cryptoService.readCertificate(CA_CERT_FILE); + expectFalse(cryptoService.isCertificateExpired(caCert)); + } + + @PluginTest + public void isClientCertificate(CryptoService cryptoService) throws Exception + { + X509Certificate clientCert = cryptoService.readCertificate(CLIENT_CERT_FILE); + X509Certificate serverCert = cryptoService.readCertificate(SERVER_CERT_FILE); + + expectTrue(cryptoService.isClientCertificate(clientCert)); + expectFalse(cryptoService.isClientCertificate(serverCert)); + } + + @PluginTest + public void isKeyPair(CryptoService cryptoService) throws Exception + { + PrivateKey clientKey = cryptoService.readPrivateKey(CLIENT_KEY_FILE, PASSWORD); + X509Certificate clientCert = cryptoService.readCertificate(CLIENT_CERT_FILE); + PrivateKey serverKey = cryptoService.readPrivateKey(SERVER_KEY_FILE, PASSWORD); + X509Certificate serverCert = cryptoService.readCertificate(SERVER_CERT_FILE); + + expectTrue(cryptoService.isKeyPair(clientKey, clientCert.getPublicKey())); + expectTrue(cryptoService.isKeyPair(serverKey, serverCert.getPublicKey())); + + expectFalse(cryptoService.isKeyPair(clientKey, serverCert.getPublicKey())); + expectFalse(cryptoService.isKeyPair(serverKey, clientCert.getPublicKey())); + } + + @PluginTest + public void isServerCertificate(CryptoService cryptoService) throws Exception + { + X509Certificate clientCert = cryptoService.readCertificate(CLIENT_CERT_FILE); + X509Certificate serverCert = cryptoService.readCertificate(SERVER_CERT_FILE); + + expectFalse(cryptoService.isServerCertificate(clientCert)); + expectTrue(cryptoService.isServerCertificate(serverCert)); + } + + @PluginTest + public void readCertificateInputStream(CryptoService cryptoService) throws Exception + { + try (InputStream in = Files.newInputStream(CA_CERT_FILE)) + { + expectNotNull(cryptoService.readCertificate(in)); + } + } + + @PluginTest + public void readCertificatePath(CryptoService cryptoService) throws Exception + { + expectNotNull(cryptoService.readCertificate(CA_CERT_FILE)); + } + + @PluginTest + public void readCertificatesInputStream(CryptoService cryptoService) throws Exception + { + try (InputStream in = Files.newInputStream(CA_CERT_FILE)) + { + List certs = cryptoService.readCertificates(in); + expectNotNull(certs); + expectSame(1, certs.size()); + } + } + + @PluginTest + public void readCertificatesPath(CryptoService cryptoService) throws Exception + { + List certs = cryptoService.readCertificates(CA_CERT_FILE); + expectNotNull(certs); + expectSame(1, certs.size()); + } + + @PluginTest + public void readKeyStoreJksInputStream(CryptoService cryptoService) throws Exception + { + try (InputStream in = Files.newInputStream(CLIENT_KEY_STORE_JKS_FILE)) + { + expectNotNull(cryptoService.readKeyStoreJks(in, PASSWORD)); + } + } + + @PluginTest + public void readKeyStoreJksPath(CryptoService cryptoService) throws Exception + { + expectNotNull(cryptoService.readKeyStoreJks(SERVER_KEY_STORE_JKS_FILE, PASSWORD)); + } + + @PluginTest + public void readKeyStorePkcs12InputStream(CryptoService cryptoService) throws Exception + { + try (InputStream in = Files.newInputStream(CLIENT_KEY_STORE_P12_FILE)) + { + cryptoService.readKeyStorePkcs12(in, PASSWORD); + } + } + + @PluginTest + public void readKeyStorePkcs12Path(CryptoService cryptoService) throws Exception + { + expectNotNull(cryptoService.readKeyStorePkcs12(SERVER_KEY_STORE_P12_FILE, PASSWORD)); + } + + @PluginTest + public void readPrivateKeyInputStream(CryptoService cryptoService) throws Exception + { + expectException(NullPointerException.class, () -> + { + try (InputStream in = Files.newInputStream(CLIENT_KEY_FILE)) + { + cryptoService.readPrivateKey(in); + } + }); + } + + @PluginTest + public void readPrivateKeyPath(CryptoService cryptoService) throws Exception + { + expectException(NullPointerException.class, () -> + { + cryptoService.readPrivateKey(SERVER_KEY_FILE); + }); + } + + @PluginTest + public void readPrivateKeyInputStreamCharArray(CryptoService cryptoService) throws Exception + { + try (InputStream in = Files.newInputStream(CLIENT_KEY_FILE)) + { + expectNotNull(cryptoService.readPrivateKey(in, PASSWORD)); + } + } + + @PluginTest + public void readPrivateKeyPathCharArray(CryptoService cryptoService) throws Exception + { + expectNotNull(cryptoService.readPrivateKey(SERVER_KEY_FILE, PASSWORD)); + } + + @PluginTest + public void validateClientCertificateCollection(CryptoService cryptoService) throws Exception + { + KeyStore caTrustStore = cryptoService.readKeyStorePkcs12(CA_TRUST_STORE_P12_FILE, PASSWORD); + X509Certificate serverCert = cryptoService.readCertificate(SERVER_CERT_FILE); + KeyStore serverTrustStore = cryptoService.createKeyStoreForTrustedCertificates(serverCert); + X509Certificate clientCert = cryptoService.readCertificate(CLIENT_CERT_FILE); + + cryptoService.validateClientCertificate(caTrustStore, List.of(clientCert)); + + expectException(CertificateException.class, + () -> cryptoService.validateClientCertificate(caTrustStore, List.of(serverCert))); + expectException(CertificateException.class, + () -> cryptoService.validateClientCertificate(serverTrustStore, List.of(clientCert))); + } + + @PluginTest + public void vaildateClientCertificateVarArgs(CryptoService cryptoService) throws Exception + { + KeyStore caTrustStore = cryptoService.readKeyStorePkcs12(CA_TRUST_STORE_JKS_FILE, PASSWORD); + X509Certificate serverCert = cryptoService.readCertificate(SERVER_CERT_FILE); + KeyStore serverTrustStore = cryptoService.createKeyStoreForTrustedCertificates(serverCert); + X509Certificate clientCert = cryptoService.readCertificate(CLIENT_CERT_FILE); + + cryptoService.validateClientCertificate(caTrustStore, clientCert); + + expectException(CertificateException.class, + () -> cryptoService.validateClientCertificate(caTrustStore, serverCert)); + expectException(CertificateException.class, + () -> cryptoService.validateClientCertificate(serverTrustStore, clientCert)); + } + + @PluginTest + public void vaildateServerCertificateCollection(CryptoService cryptoService) throws Exception + { + KeyStore caTrustStore = cryptoService.readKeyStorePkcs12(CA_TRUST_STORE_P12_FILE, PASSWORD); + X509Certificate serverCert = cryptoService.readCertificate(SERVER_CERT_FILE); + X509Certificate clientCert = cryptoService.readCertificate(CLIENT_CERT_FILE); + KeyStore clientTrustStore = cryptoService.createKeyStoreForTrustedCertificates(clientCert); + + cryptoService.validateServerCertificate(caTrustStore, List.of(serverCert)); + + expectException(CertificateException.class, + () -> cryptoService.validateServerCertificate(caTrustStore, List.of(clientCert))); + expectException(CertificateException.class, + () -> cryptoService.validateServerCertificate(clientTrustStore, List.of(serverCert))); + } + + @PluginTest + public void vaildateServerCertificateVarArgs(CryptoService cryptoService) throws Exception + { + KeyStore caTrustStore = cryptoService.readKeyStorePkcs12(CA_TRUST_STORE_JKS_FILE, PASSWORD); + X509Certificate serverCert = cryptoService.readCertificate(SERVER_CERT_FILE); + X509Certificate clientCert = cryptoService.readCertificate(CLIENT_CERT_FILE); + KeyStore clientTrustStore = cryptoService.createKeyStoreForTrustedCertificates(serverCert); + + cryptoService.validateServerCertificate(caTrustStore, serverCert); + + expectException(CertificateException.class, + () -> cryptoService.validateServerCertificate(caTrustStore, clientCert)); + expectException(CertificateException.class, + () -> cryptoService.validateServerCertificate(clientTrustStore, serverCert)); + } +} diff --git a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/spring/config/Config.java b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/spring/config/Config.java index 9e9b002a8..2f18e87a9 100644 --- a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/spring/config/Config.java +++ b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/spring/config/Config.java @@ -10,6 +10,7 @@ import dev.dsf.bpe.test.service.ApiTest; import dev.dsf.bpe.test.service.ContinueSendTest; import dev.dsf.bpe.test.service.ContinueSendTestEvaluate; +import dev.dsf.bpe.test.service.CryptoServiceTest; import dev.dsf.bpe.test.service.EndpointProviderTest; import dev.dsf.bpe.test.service.ErrorBoundaryEventTestThrow; import dev.dsf.bpe.test.service.ErrorBoundaryEventTestVerify; @@ -34,6 +35,6 @@ public ActivityPrototypeBeanCreator activityPrototypeBeanCreator() StartSendTaskTestListener.class, SendTaskTest.class, StartFieldInjectionTestListener.class, FieldInjectionTest.class, ErrorBoundaryEventTestThrow.class, ErrorBoundaryEventTestVerify.class, ExceptionTest.class, ContinueSendTest.class, ContinueSendTestSend.class, ContinueSendTestEvaluate.class, - JsonVariableTestSet.class, JsonVariableTestGet.class); + JsonVariableTestSet.class, JsonVariableTestGet.class, CryptoServiceTest.class); } } diff --git a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/resources/bpe/test.bpmn b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/resources/bpe/test.bpmn index 44307bf9f..2e1aa6c61 100644 --- a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/resources/bpe/test.bpmn +++ b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/resources/bpe/test.bpmn @@ -33,6 +33,7 @@ Flow_0jv5jil Flow_0xzipbl Flow_0kt0v58 + Flow_1g6h0ul @@ -54,6 +55,7 @@ Flow_1p9dw3m Flow_1n773yf Flow_0x6aoim + Flow_0b2eolg Flow_0a1kwg9 @@ -235,6 +237,14 @@ ${testActivity == 'JsonVariableTest'} + + Flow_1g6h0ul + Flow_0b2eolg + + + ${testActivity == 'CryptoServiceTest'} + + @@ -335,6 +345,10 @@ + + + + @@ -502,6 +516,16 @@ + + + + + + + + + + diff --git a/dsf-bpe/dsf-bpe-test-plugin/src/main/java/dev/dsf/bpe/test/PluginTestExecutor.java b/dsf-bpe/dsf-bpe-test-plugin/src/main/java/dev/dsf/bpe/test/PluginTestExecutor.java index 5684f8dd0..e4ae17d3c 100644 --- a/dsf-bpe/dsf-bpe-test-plugin/src/main/java/dev/dsf/bpe/test/PluginTestExecutor.java +++ b/dsf-bpe/dsf-bpe-test-plugin/src/main/java/dev/dsf/bpe/test/PluginTestExecutor.java @@ -23,6 +23,12 @@ public TestAssertException(String message) } } + @FunctionalInterface + public interface RunnableWithException + { + void run() throws Exception; + } + public static final void execute(Object testClass, Consumer addTestSucceededToStartTask, Consumer addTestFailedToStartTask, Runnable updateStartTask, Object testMethodArg0, Object testMethodArg1, Object... testMethodArgs) @@ -188,7 +194,7 @@ private static TestAssertException createTestAssertExceptionNotSame(String type, "Tested " + type + " is not same as expected [expected: " + expected + ", actual: " + actual + "]"); } - public static void expectException(Class expectedException, Runnable run) + public static void expectException(Class expectedException, RunnableWithException run) { Objects.requireNonNull(expectedException, "expectedException"); Objects.requireNonNull(run, "run");