From aee30ab51d1c20874d01a5aceecce7a470ab6d1f Mon Sep 17 00:00:00 2001 From: Philip Helger Date: Thu, 19 Mar 2026 07:42:14 +0100 Subject: [PATCH 1/6] Ignore more --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 380d07353..5b33414c7 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ base-example-large-*.xml private-*.properties* Main*ReceiverProd.java +.claude/ .idea/ .settings/ target/ From 0c7655fbe71505e7ca227b23a8bc7020663e9ed7 Mon Sep 17 00:00:00 2001 From: Philip Helger Date: Thu, 19 Mar 2026 07:47:00 +0100 Subject: [PATCH 2/6] Added additional crypt parameter configuration --- .../helger/phase4/crypto/AS4CryptParams.java | 136 ++++++++++++++++++ .../crypto/AS4CryptoFactoryConfiguration.java | 45 +++--- .../crypto/ECryptoKeyAgreementMethod.java | 69 +++++++++ .../crypto/ECryptoKeyDerivationMethod.java | 67 +++++++++ .../crypto/ECryptoKeyWrapAlgorithm.java | 71 +++++++++ .../phase4/messaging/crypto/AS4Encryptor.java | 59 ++++++-- 6 files changed, 408 insertions(+), 39 deletions(-) create mode 100644 phase4-lib/src/main/java/com/helger/phase4/crypto/ECryptoKeyAgreementMethod.java create mode 100644 phase4-lib/src/main/java/com/helger/phase4/crypto/ECryptoKeyDerivationMethod.java create mode 100644 phase4-lib/src/main/java/com/helger/phase4/crypto/ECryptoKeyWrapAlgorithm.java diff --git a/phase4-lib/src/main/java/com/helger/phase4/crypto/AS4CryptParams.java b/phase4-lib/src/main/java/com/helger/phase4/crypto/AS4CryptParams.java index e8c63a54e..adcce504c 100644 --- a/phase4-lib/src/main/java/com/helger/phase4/crypto/AS4CryptParams.java +++ b/phase4-lib/src/main/java/com/helger/phase4/crypto/AS4CryptParams.java @@ -56,6 +56,13 @@ public class AS4CryptParams implements ICloneable public static final ICryptoSessionKeyProvider DEFAULT_SESSION_KEY_PROVIDER = ICryptoSessionKeyProvider.INSTANCE_RANDOM_AES_128; public static final boolean DEFAULT_ENCRYPT_SYMMETRIC_SESSION_KEY = true; + /** + * HKDF PRF algorithm URI for HMAC-SHA256, as required by eDelivery AS4 2.0 + * + * @since 4.4.0 + */ + public static final String HKDF_PRF_HMAC_SHA256 = WSS4JConstants.HMAC_SHA256; + private static final Logger LOGGER = Phase4LoggerFactory.getLogger (AS4CryptParams.class); // The key identifier type to use @@ -68,6 +75,14 @@ public class AS4CryptParams implements ICloneable private String m_sMGFAlgorithm = DEFAULT_MGF_ALGORITHM; // The digest algorithm to use with the RSA-OAEP key transport algorithm private String m_sDigestAlgorithm = DEFAULT_DIGEST_ALGORITHM; + // Key agreement method (e.g. X25519, X448, ECDH-ES) - null means no key + // agreement (use key transport instead) + private ECryptoKeyAgreementMethod m_eKeyAgreementMethod; + // Key derivation function (e.g. HKDF, ConcatKDF) - only used with key + // agreement + private ECryptoKeyDerivationMethod m_eKeyDerivationMethod; + // Key wrap algorithm (e.g. AES-128 KeyWrap) - only used with key agreement + private ECryptoKeyWrapAlgorithm m_eKeyWrapAlgorithm; // The explicit certificate to use - has precedence over the alias private X509Certificate m_aCert; // The alias into the WSS4J crypto config @@ -220,6 +235,121 @@ public final AS4CryptParams setDigestAlgorithm (@NonNull @Nonempty final String return this; } + /** + * @return The key agreement method to use. May be null, in which case key transport + * (e.g. RSA-OAEP) is used instead of key agreement. + * @since 4.4.0 + */ + @Nullable + public final ECryptoKeyAgreementMethod getKeyAgreementMethod () + { + return m_eKeyAgreementMethod; + } + + /** + * @return true if a key agreement method is set, false if not. + * @since 4.4.0 + */ + public final boolean hasKeyAgreementMethod () + { + return m_eKeyAgreementMethod != null; + } + + /** + * Set the key agreement method to use. When set, the encryption will use key agreement (e.g. + * ECDH-ES, X25519) instead of key transport (e.g. RSA-OAEP). If set to null, key + * transport is used. + * + * @param eKeyAgreementMethod + * The key agreement method. May be null. + * @return this for chaining + * @since 4.4.0 + */ + @NonNull + public final AS4CryptParams setKeyAgreementMethod (@Nullable final ECryptoKeyAgreementMethod eKeyAgreementMethod) + { + m_eKeyAgreementMethod = eKeyAgreementMethod; + return this; + } + + /** + * @return The key derivation function to use with key agreement. May be null. + * @since 4.4.0 + */ + @Nullable + public final ECryptoKeyDerivationMethod getKeyDerivationMethod () + { + return m_eKeyDerivationMethod; + } + + /** + * Set the key derivation function to use with key agreement (e.g. HKDF, ConcatKDF). + * + * @param eKeyDerivationMethod + * The key derivation method. May be null. + * @return this for chaining + * @since 4.4.0 + */ + @NonNull + public final AS4CryptParams setKeyDerivationMethod (@Nullable final ECryptoKeyDerivationMethod eKeyDerivationMethod) + { + m_eKeyDerivationMethod = eKeyDerivationMethod; + return this; + } + + /** + * @return The key wrap algorithm to use with key agreement. May be null. + * @since 4.4.0 + */ + @Nullable + public final ECryptoKeyWrapAlgorithm getKeyWrapAlgorithm () + { + return m_eKeyWrapAlgorithm; + } + + /** + * Set the key wrap algorithm to use with key agreement (e.g. AES-128 KeyWrap). + * + * @param eKeyWrapAlgorithm + * The key wrap algorithm. May be null. + * @return this for chaining + * @since 4.4.0 + */ + @NonNull + public final AS4CryptParams setKeyWrapAlgorithm (@Nullable final ECryptoKeyWrapAlgorithm eKeyWrapAlgorithm) + { + m_eKeyWrapAlgorithm = eKeyWrapAlgorithm; + return this; + } + + /** + * Convenience method to set all parameters required for eDelivery AS4 2.0 EdDSA/X25519 key + * agreement: X25519 key agreement, HKDF key derivation, AES-128 key wrap. + * + * @return this for chaining + * @since 4.4.0 + */ + @NonNull + public final AS4CryptParams setEDelivery2KeyAgreementX25519 () + { + return setKeyAgreementMethod (ECryptoKeyAgreementMethod.X25519).setKeyDerivationMethod (ECryptoKeyDerivationMethod.HKDF) + .setKeyWrapAlgorithm (ECryptoKeyWrapAlgorithm.AES_128); + } + + /** + * Convenience method to set all parameters required for eDelivery AS4 2.0 ECDSA/ECDH-ES key + * agreement: ECDH-ES key agreement, HKDF key derivation, AES-128 key wrap. + * + * @return this for chaining + * @since 4.4.0 + */ + @NonNull + public final AS4CryptParams setEDelivery2KeyAgreementECDHES () + { + return setKeyAgreementMethod (ECryptoKeyAgreementMethod.ECDH_ES).setKeyDerivationMethod (ECryptoKeyDerivationMethod.HKDF) + .setKeyWrapAlgorithm (ECryptoKeyWrapAlgorithm.AES_128); + } + /** * @return The currently set X509 certificate. May be null. */ @@ -462,6 +592,9 @@ public void cloneTo (@NonNull final AS4CryptParams aTarget) .setKeyEncAlgorithm (m_eKeyEncAlgorithm) .setMGFAlgorithm (m_sMGFAlgorithm) .setDigestAlgorithm (m_sDigestAlgorithm) + .setKeyAgreementMethod (m_eKeyAgreementMethod) + .setKeyDerivationMethod (m_eKeyDerivationMethod) + .setKeyWrapAlgorithm (m_eKeyWrapAlgorithm) .setCertificate (m_aCert) .setAlias (m_sAlias) .setSessionKeyProvider (m_aSessionKeyProvider) @@ -488,6 +621,9 @@ public String toString () .append ("KeyEncAlgorithm", m_eKeyEncAlgorithm) .append ("MGFAlgorithm", m_sMGFAlgorithm) .append ("DigestAlgorithm", m_sDigestAlgorithm) + .appendIfNotNull ("KeyAgreementMethod", m_eKeyAgreementMethod) + .appendIfNotNull ("KeyDerivationMethod", m_eKeyDerivationMethod) + .appendIfNotNull ("KeyWrapAlgorithm", m_eKeyWrapAlgorithm) .append ("Certificate", m_aCert) .append ("Alias", m_sAlias) .append ("SessionKeyProvider", m_aSessionKeyProvider) diff --git a/phase4-lib/src/main/java/com/helger/phase4/crypto/AS4CryptoFactoryConfiguration.java b/phase4-lib/src/main/java/com/helger/phase4/crypto/AS4CryptoFactoryConfiguration.java index 3a7da34ed..b31da33d6 100644 --- a/phase4-lib/src/main/java/com/helger/phase4/crypto/AS4CryptoFactoryConfiguration.java +++ b/phase4-lib/src/main/java/com/helger/phase4/crypto/AS4CryptoFactoryConfiguration.java @@ -38,11 +38,10 @@ import com.helger.security.keystore.LoadedKeyStore; /** - * phase4 crypto factory settings based on {@link IConfig}. The configuration - * elements are solely taken from the global configuration and not from - * arbitrary files. Multiple different crypto factory configurations can be - * handled uses different configuration property prefixes. This class only - * supports {@link Merlin} as the crypto implementation. + * phase4 crypto factory settings based on {@link IConfig}. The configuration elements are solely + * taken from the global configuration and not from arbitrary files. Multiple different crypto + * factory configurations can be handled uses different configuration property prefixes. This class + * only supports {@link Merlin} as the crypto implementation. * * @author Philip Helger * @since 3.0.0 @@ -53,9 +52,8 @@ public class AS4CryptoFactoryConfiguration extends AS4CryptoFactoryInMemoryKeySt private static final Logger LOGGER = Phase4LoggerFactory.getLogger (AS4CryptoFactoryConfiguration.class); /** - * @return The default instance, created by reading the default properties - * from the configuration sources (application.properties, environment - * variables and Java system properties). + * @return The default instance, created by reading the default properties from the configuration + * sources (application.properties, environment variables and Java system properties). * @throws Phase4RuntimeException * if one of the mandatory configuration parameters is not present. */ @@ -68,8 +66,8 @@ public static AS4CryptoFactoryConfiguration getDefaultInstance () throws Phase4R } /** - * Same as {@link #getDefaultInstance()} just that it returns - * null instead of throwing a RuntimeException. + * Same as {@link #getDefaultInstance()} just that it returns null instead of + * throwing a RuntimeException. * * @return null in case of error. */ @@ -93,8 +91,8 @@ public static AS4CryptoFactoryConfiguration getDefaultInstanceOrNull () private final ITrustStoreDescriptor m_aTrustStorDesc; /** - * This constructor takes the configuration object and uses the default prefix - * for backwards compatibility. This is kind of the default constructor. + * This constructor takes the configuration object and uses the default prefix for backwards + * compatibility. This is kind of the default constructor. * * @param aConfig * The configuration object to be used. May not be null. @@ -175,14 +173,14 @@ private static ITrustStoreDescriptor _loadTrustStore (@NonNull final IConfigWith } /** - * This constructor takes the configuration object and uses the provided - * configuration prefix. This is kind of the default constructor. + * This constructor takes the configuration object and uses the provided configuration prefix. + * This is kind of the default constructor. * * @param aConfig * The configuration object to be used. May not be null. * @param sConfigPrefix - * The configuration prefix to be used. May neither be - * null nor empty and must end with a dot ('.'). + * The configuration prefix to be used. May neither be null nor empty and must + * end with a dot ('.'). * @throws Phase4RuntimeException * If loading the key store configuration from configuration fails. */ @@ -194,14 +192,14 @@ public AS4CryptoFactoryConfiguration (@NonNull final IConfigWithFallback aConfig } /** - * This constructor takes the configuration object and uses the provided - * configuration prefix. This is kind of the default constructor. + * This constructor takes the configuration object and uses the provided configuration prefix. + * This is kind of the default constructor. * * @param aConfig * The configuration object to be used. May not be null. * @param sConfigPrefix - * The configuration prefix to be used. May neither be - * null nor empty and must end with a dot ('.'). + * The configuration prefix to be used. May neither be null nor empty and must + * end with a dot ('.'). * @param bLogError * true if errors should be logged if loading fails. * @throws Phase4RuntimeException @@ -220,8 +218,8 @@ public AS4CryptoFactoryConfiguration (@NonNull final IConfigWithFallback aConfig * @param aKeyStoreDesc * The key store descriptor. May not be null. * @param aTrustStoreDesc - * The trust store descriptor. May be null in which case - * the global JRE CA certs list will be used. + * The trust store descriptor. May be null in which case the global JRE CA + * certs list will be used. */ private AS4CryptoFactoryConfiguration (@NonNull final IKeyStoreAndKeyDescriptor aKeyStoreDesc, @Nullable final ITrustStoreDescriptor aTrustStoreDesc) @@ -241,8 +239,7 @@ public IKeyStoreAndKeyDescriptor getKeyStoreDescriptor () } /** - * @return The descriptor used to load the trust store. Never - * null. + * @return The descriptor used to load the trust store. Never null. */ @NonNull public ITrustStoreDescriptor getTrustStoreDescriptor () diff --git a/phase4-lib/src/main/java/com/helger/phase4/crypto/ECryptoKeyAgreementMethod.java b/phase4-lib/src/main/java/com/helger/phase4/crypto/ECryptoKeyAgreementMethod.java new file mode 100644 index 000000000..feb98a4ad --- /dev/null +++ b/phase4-lib/src/main/java/com/helger/phase4/crypto/ECryptoKeyAgreementMethod.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015-2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.crypto; + +import org.apache.wss4j.common.WSS4JConstants; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import com.helger.annotation.Nonempty; +import com.helger.base.id.IHasID; +import com.helger.base.lang.EnumHelper; + +/** + * Enumeration of key agreement methods for XML Encryption. Key agreement is an alternative to key + * transport (e.g. RSA-OAEP) where both parties contribute to deriving a shared secret. + * + * @author Philip Helger + * @since 4.4.0 + */ +public enum ECryptoKeyAgreementMethod implements IHasID +{ + /** ECDH-ES key agreement (generic, for EC keys on standard curves like secp256r1) */ + ECDH_ES (WSS4JConstants.AGREEMENT_METHOD_ECDH_ES), + /** X25519 key agreement (Curve25519, eDelivery AS4 2.0 Common Usage Profile) */ + X25519 (WSS4JConstants.AGREEMENT_METHOD_X25519), + /** X448 key agreement (Curve448) */ + X448 (WSS4JConstants.AGREEMENT_METHOD_X448); + + private final String m_sID; + + ECryptoKeyAgreementMethod (@NonNull @Nonempty final String sID) + { + m_sID = sID; + } + + @NonNull + @Nonempty + public String getID () + { + return m_sID; + } + + @Nullable + public static ECryptoKeyAgreementMethod getFromIDOrNull (@Nullable final String sID) + { + return EnumHelper.getFromIDOrNull (ECryptoKeyAgreementMethod.class, sID); + } + + @Nullable + public static ECryptoKeyAgreementMethod getFromIDOrDefault (@Nullable final String sID, + @Nullable final ECryptoKeyAgreementMethod eDefault) + { + return EnumHelper.getFromIDOrDefault (ECryptoKeyAgreementMethod.class, sID, eDefault); + } +} diff --git a/phase4-lib/src/main/java/com/helger/phase4/crypto/ECryptoKeyDerivationMethod.java b/phase4-lib/src/main/java/com/helger/phase4/crypto/ECryptoKeyDerivationMethod.java new file mode 100644 index 000000000..fb67e8a93 --- /dev/null +++ b/phase4-lib/src/main/java/com/helger/phase4/crypto/ECryptoKeyDerivationMethod.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015-2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.crypto; + +import org.apache.wss4j.common.WSS4JConstants; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import com.helger.annotation.Nonempty; +import com.helger.base.id.IHasID; +import com.helger.base.lang.EnumHelper; + +/** + * Enumeration of key derivation functions used in key agreement schemes. The key derivation + * function takes the shared secret from key agreement and derives the actual key encryption key. + * + * @author Philip Helger + * @since 4.4.0 + */ +public enum ECryptoKeyDerivationMethod implements IHasID +{ + /** ConcatKDF (NIST SP 800-56A) */ + CONCAT_KDF (WSS4JConstants.KEYDERIVATION_CONCATKDF), + /** HKDF (RFC 5869) - eDelivery AS4 2.0 */ + HKDF (WSS4JConstants.KEYDERIVATION_HKDF); + + private final String m_sID; + + ECryptoKeyDerivationMethod (@NonNull @Nonempty final String sID) + { + m_sID = sID; + } + + @NonNull + @Nonempty + public String getID () + { + return m_sID; + } + + @Nullable + public static ECryptoKeyDerivationMethod getFromIDOrNull (@Nullable final String sID) + { + return EnumHelper.getFromIDOrNull (ECryptoKeyDerivationMethod.class, sID); + } + + @Nullable + public static ECryptoKeyDerivationMethod getFromIDOrDefault (@Nullable final String sID, + @Nullable final ECryptoKeyDerivationMethod eDefault) + { + return EnumHelper.getFromIDOrDefault (ECryptoKeyDerivationMethod.class, sID, eDefault); + } +} diff --git a/phase4-lib/src/main/java/com/helger/phase4/crypto/ECryptoKeyWrapAlgorithm.java b/phase4-lib/src/main/java/com/helger/phase4/crypto/ECryptoKeyWrapAlgorithm.java new file mode 100644 index 000000000..33020fd96 --- /dev/null +++ b/phase4-lib/src/main/java/com/helger/phase4/crypto/ECryptoKeyWrapAlgorithm.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015-2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.crypto; + +import org.apache.wss4j.common.WSS4JConstants; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import com.helger.annotation.Nonempty; +import com.helger.base.id.IHasID; +import com.helger.base.lang.EnumHelper; + +/** + * Enumeration of key wrap algorithms used in key agreement schemes. The key wrap algorithm wraps + * the content encryption key using the derived key from key derivation. + * + * @author Philip Helger + * @since 4.4.0 + */ +public enum ECryptoKeyWrapAlgorithm implements IHasID +{ + /** AES-128 Key Wrap (eDelivery AS4 2.0) */ + AES_128 (WSS4JConstants.KEYWRAP_AES128), + /** AES-192 Key Wrap */ + AES_192 (WSS4JConstants.KEYWRAP_AES192), + /** AES-256 Key Wrap */ + AES_256 (WSS4JConstants.KEYWRAP_AES256), + /** Triple DES Key Wrap */ + TRIPLE_DES (WSS4JConstants.KEYWRAP_TRIPLEDES); + + private final String m_sID; + + ECryptoKeyWrapAlgorithm (@NonNull @Nonempty final String sID) + { + m_sID = sID; + } + + @NonNull + @Nonempty + public String getID () + { + return m_sID; + } + + @Nullable + public static ECryptoKeyWrapAlgorithm getFromIDOrNull (@Nullable final String sID) + { + return EnumHelper.getFromIDOrNull (ECryptoKeyWrapAlgorithm.class, sID); + } + + @Nullable + public static ECryptoKeyWrapAlgorithm getFromIDOrDefault (@Nullable final String sID, + @Nullable final ECryptoKeyWrapAlgorithm eDefault) + { + return EnumHelper.getFromIDOrDefault (ECryptoKeyWrapAlgorithm.class, sID, eDefault); + } +} diff --git a/phase4-lib/src/main/java/com/helger/phase4/messaging/crypto/AS4Encryptor.java b/phase4-lib/src/main/java/com/helger/phase4/messaging/crypto/AS4Encryptor.java index 84c526b2c..8cde9e0da 100644 --- a/phase4-lib/src/main/java/com/helger/phase4/messaging/crypto/AS4Encryptor.java +++ b/phase4-lib/src/main/java/com/helger/phase4/messaging/crypto/AS4Encryptor.java @@ -80,11 +80,28 @@ private static WSSecEncrypt _createEncrypt (@NonNull final WSSecHeader aSecHeade // signing certificate. aBuilder.setKeyIdentifierType (aCryptParams.getKeyIdentifierType ().getTypeID ()); aBuilder.setSymmetricEncAlgorithm (aCryptParams.getAlgorithmCrypt ().getAlgorithmURI ()); - aBuilder.setKeyEncAlgo (aCryptParams.getKeyEncAlgorithm ().getID ()); - aBuilder.setMGFAlgorithm (aCryptParams.getMGFAlgorithm ()); - aBuilder.setDigestAlgorithm (aCryptParams.getDigestAlgorithm ()); aBuilder.setEncryptSymmKey (aCryptParams.isEncryptSymmetricSessionKey ()); + if (aCryptParams.hasKeyAgreementMethod ()) + { + // eDelivery AS4 2.0 style: key agreement (X25519/ECDH-ES) + key + // derivation (HKDF) + key wrap (AES-128) + aBuilder.setKeyAgreementMethod (aCryptParams.getKeyAgreementMethod ().getID ()); + + if (aCryptParams.getKeyDerivationMethod () != null) + aBuilder.setKeyDerivationMethod (aCryptParams.getKeyDerivationMethod ().getID ()); + + if (aCryptParams.getKeyWrapAlgorithm () != null) + aBuilder.setKeyEncAlgo (aCryptParams.getKeyWrapAlgorithm ().getID ()); + } + else + { + // Classic RSA key transport mode + aBuilder.setKeyEncAlgo (aCryptParams.getKeyEncAlgorithm ().getID ()); + aBuilder.setMGFAlgorithm (aCryptParams.getMGFAlgorithm ()); + aBuilder.setDigestAlgorithm (aCryptParams.getDigestAlgorithm ()); + } + if (aCryptParams.hasCertificate ()) { // Certificate was provided externally @@ -121,12 +138,18 @@ private static Document _encryptSoapBodyPayload (@NonNull final IAS4CryptoFactor aCryptParams.getKeyIdentifierType ().name () + "; EncAlgo=" + aCryptParams.getAlgorithmCrypt ().getAlgorithmURI () + - "; KeyEncAlgo=" + - aCryptParams.getKeyEncAlgorithm () + - "; MgfAlgo=" + - aCryptParams.getMGFAlgorithm () + - "; DigestAlgo=" + - aCryptParams.getDigestAlgorithm () + + (aCryptParams.hasKeyAgreementMethod () ? "; KeyAgreement=" + + aCryptParams.getKeyAgreementMethod () + + "; KeyDerivation=" + + aCryptParams.getKeyDerivationMethod () + + "; KeyWrap=" + + aCryptParams.getKeyWrapAlgorithm () + : "; KeyEncAlgo=" + + aCryptParams.getKeyEncAlgorithm () + + "; MgfAlgo=" + + aCryptParams.getMGFAlgorithm () + + "; DigestAlgo=" + + aCryptParams.getDigestAlgorithm ()) + (aCryptParams.hasAlias () ? "; KeyAlias=" + aCryptParams.getAlias () : "") + (aCryptParams.hasCertificate () ? "; CertificateSubjectCN=" + aCryptParams.getCertificate ().getSubjectX500Principal ().getName () @@ -221,12 +244,18 @@ private static AS4MimeMessage _encryptToMimeMessage (@NonNull final ESoapVersion aCryptParams.getKeyIdentifierType ().name () + "; EncAlgo=" + aCryptParams.getAlgorithmCrypt ().getAlgorithmURI () + - "; KeyEncAlgo=" + - aCryptParams.getKeyEncAlgorithm () + - "; MgfAlgo=" + - aCryptParams.getMGFAlgorithm () + - "; DigestAlgo=" + - aCryptParams.getDigestAlgorithm () + + (aCryptParams.hasKeyAgreementMethod () ? "; KeyAgreement=" + + aCryptParams.getKeyAgreementMethod () + + "; KeyDerivation=" + + aCryptParams.getKeyDerivationMethod () + + "; KeyWrap=" + + aCryptParams.getKeyWrapAlgorithm () + : "; KeyEncAlgo=" + + aCryptParams.getKeyEncAlgorithm () + + "; MgfAlgo=" + + aCryptParams.getMGFAlgorithm () + + "; DigestAlgo=" + + aCryptParams.getDigestAlgorithm ()) + (aCryptParams.hasAlias () ? "; KeyAlias=" + aCryptParams.getAlias () : "") + (aCryptParams.hasCertificate () ? "; CertificateSubjectCN=" + aCryptParams.getCertificate ().getSubjectX500Principal ().getName () From 48f987b9db1776c3a0847090bf16ad061645a634 Mon Sep 17 00:00:00 2001 From: Philip Helger Date: Thu, 19 Mar 2026 09:18:22 +0100 Subject: [PATCH 3/6] Added tests --- .../phase4/crypto/AS4CryptParamsTest.java | 86 +++++++++++++++++++ .../crypto/ECryptoKeyAgreementMethodTest.java | 43 ++++++++++ .../ECryptoKeyDerivationMethodTest.java | 43 ++++++++++ .../crypto/ECryptoKeyWrapAlgorithmTest.java | 43 ++++++++++ 4 files changed, 215 insertions(+) create mode 100644 phase4-lib/src/test/java/com/helger/phase4/crypto/AS4CryptParamsTest.java create mode 100644 phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyAgreementMethodTest.java create mode 100644 phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyDerivationMethodTest.java create mode 100644 phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyWrapAlgorithmTest.java diff --git a/phase4-lib/src/test/java/com/helger/phase4/crypto/AS4CryptParamsTest.java b/phase4-lib/src/test/java/com/helger/phase4/crypto/AS4CryptParamsTest.java new file mode 100644 index 000000000..e2bd81123 --- /dev/null +++ b/phase4-lib/src/test/java/com/helger/phase4/crypto/AS4CryptParamsTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015-2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.crypto; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * Test class for class {@link AS4CryptParams}. + * + * @author Philip Helger + */ +public final class AS4CryptParamsTest +{ + @Test + public void testDefaultValues () + { + final AS4CryptParams aParams = new AS4CryptParams (); + assertNull (aParams.getAlgorithmCrypt ()); + assertNull (aParams.getKeyAgreementMethod ()); + assertNull (aParams.getKeyDerivationMethod ()); + assertNull (aParams.getKeyWrapAlgorithm ()); + assertFalse (aParams.hasKeyAgreementMethod ()); + assertEquals (ECryptoKeyEncryptionAlgorithm.RSA_OAEP_XENC11, aParams.getKeyEncAlgorithm ()); + } + + @Test + public void testEDelivery2X25519 () + { + final AS4CryptParams aParams = new AS4CryptParams (); + aParams.setAlgorithmCrypt (ECryptoAlgorithmCrypt.AES_128_GCM) + .setEDelivery2KeyAgreementX25519 (); + + assertTrue (aParams.hasKeyAgreementMethod ()); + assertEquals (ECryptoKeyAgreementMethod.X25519, aParams.getKeyAgreementMethod ()); + assertEquals (ECryptoKeyDerivationMethod.HKDF, aParams.getKeyDerivationMethod ()); + assertEquals (ECryptoKeyWrapAlgorithm.AES_128, aParams.getKeyWrapAlgorithm ()); + } + + @Test + public void testEDelivery2ECDHES () + { + final AS4CryptParams aParams = new AS4CryptParams (); + aParams.setAlgorithmCrypt (ECryptoAlgorithmCrypt.AES_128_GCM) + .setEDelivery2KeyAgreementECDHES (); + + assertTrue (aParams.hasKeyAgreementMethod ()); + assertEquals (ECryptoKeyAgreementMethod.ECDH_ES, aParams.getKeyAgreementMethod ()); + assertEquals (ECryptoKeyDerivationMethod.HKDF, aParams.getKeyDerivationMethod ()); + assertEquals (ECryptoKeyWrapAlgorithm.AES_128, aParams.getKeyWrapAlgorithm ()); + } + + @Test + public void testCloneWithKeyAgreement () + { + final AS4CryptParams aParams = new AS4CryptParams (); + aParams.setAlgorithmCrypt (ECryptoAlgorithmCrypt.AES_128_GCM) + .setEDelivery2KeyAgreementX25519 (); + + final AS4CryptParams aClone = aParams.getClone (); + assertNotNull (aClone); + assertEquals (ECryptoKeyAgreementMethod.X25519, aClone.getKeyAgreementMethod ()); + assertEquals (ECryptoKeyDerivationMethod.HKDF, aClone.getKeyDerivationMethod ()); + assertEquals (ECryptoKeyWrapAlgorithm.AES_128, aClone.getKeyWrapAlgorithm ()); + assertEquals (ECryptoAlgorithmCrypt.AES_128_GCM, aClone.getAlgorithmCrypt ()); + } +} diff --git a/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyAgreementMethodTest.java b/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyAgreementMethodTest.java new file mode 100644 index 000000000..0de4e613d --- /dev/null +++ b/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyAgreementMethodTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2015-2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.crypto; + +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.helger.base.string.StringHelper; + +/** + * Test class for class {@link ECryptoKeyAgreementMethod}. + * + * @author Philip Helger + */ +public final class ECryptoKeyAgreementMethodTest +{ + @Test + public void testBasic () + { + for (final ECryptoKeyAgreementMethod e : ECryptoKeyAgreementMethod.values ()) + { + assertTrue (StringHelper.isNotEmpty (e.getID ())); + assertSame (e, ECryptoKeyAgreementMethod.getFromIDOrNull (e.getID ())); + assertSame (e, ECryptoKeyAgreementMethod.getFromIDOrDefault (e.getID (), null)); + } + } +} diff --git a/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyDerivationMethodTest.java b/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyDerivationMethodTest.java new file mode 100644 index 000000000..189a194ce --- /dev/null +++ b/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyDerivationMethodTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2015-2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.crypto; + +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.helger.base.string.StringHelper; + +/** + * Test class for class {@link ECryptoKeyDerivationMethod}. + * + * @author Philip Helger + */ +public final class ECryptoKeyDerivationMethodTest +{ + @Test + public void testBasic () + { + for (final ECryptoKeyDerivationMethod e : ECryptoKeyDerivationMethod.values ()) + { + assertTrue (StringHelper.isNotEmpty (e.getID ())); + assertSame (e, ECryptoKeyDerivationMethod.getFromIDOrNull (e.getID ())); + assertSame (e, ECryptoKeyDerivationMethod.getFromIDOrDefault (e.getID (), null)); + } + } +} diff --git a/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyWrapAlgorithmTest.java b/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyWrapAlgorithmTest.java new file mode 100644 index 000000000..259cae7cb --- /dev/null +++ b/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyWrapAlgorithmTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2015-2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.crypto; + +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.helger.base.string.StringHelper; + +/** + * Test class for class {@link ECryptoKeyWrapAlgorithm}. + * + * @author Philip Helger + */ +public final class ECryptoKeyWrapAlgorithmTest +{ + @Test + public void testBasic () + { + for (final ECryptoKeyWrapAlgorithm e : ECryptoKeyWrapAlgorithm.values ()) + { + assertTrue (StringHelper.isNotEmpty (e.getID ())); + assertSame (e, ECryptoKeyWrapAlgorithm.getFromIDOrNull (e.getID ())); + assertSame (e, ECryptoKeyWrapAlgorithm.getFromIDOrDefault (e.getID (), null)); + } + } +} From b72a9e19806944aa8a8f7970c47a350412d06478 Mon Sep 17 00:00:00 2001 From: Philip Helger Date: Thu, 19 Mar 2026 09:39:02 +0100 Subject: [PATCH 4/6] Updated tests --- .../phase4/crypto/AS4CryptParamsTest.java | 119 ++++++++++++++++++ .../crypto/ECryptoKeyAgreementMethodTest.java | 27 ++++ .../ECryptoKeyDerivationMethodTest.java | 26 ++++ .../ECryptoKeyEncryptionAlgorithmTest.java | 55 ++++++++ .../crypto/ECryptoKeyIdentifierTypeTest.java | 74 +++++++++++ .../crypto/ECryptoKeyWrapAlgorithmTest.java | 28 +++++ .../helger/phase4/crypto/ECryptoModeTest.java | 40 ++++++ 7 files changed, 369 insertions(+) create mode 100644 phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyEncryptionAlgorithmTest.java create mode 100644 phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyIdentifierTypeTest.java create mode 100644 phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoModeTest.java diff --git a/phase4-lib/src/test/java/com/helger/phase4/crypto/AS4CryptParamsTest.java b/phase4-lib/src/test/java/com/helger/phase4/crypto/AS4CryptParamsTest.java index e2bd81123..616601550 100644 --- a/phase4-lib/src/test/java/com/helger/phase4/crypto/AS4CryptParamsTest.java +++ b/phase4-lib/src/test/java/com/helger/phase4/crypto/AS4CryptParamsTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import org.junit.Test; @@ -41,6 +42,43 @@ public void testDefaultValues () assertNull (aParams.getKeyWrapAlgorithm ()); assertFalse (aParams.hasKeyAgreementMethod ()); assertEquals (ECryptoKeyEncryptionAlgorithm.RSA_OAEP_XENC11, aParams.getKeyEncAlgorithm ()); + assertEquals (ECryptoKeyIdentifierType.BST_DIRECT_REFERENCE, aParams.getKeyIdentifierType ()); + assertFalse (aParams.hasCertificate ()); + assertFalse (aParams.hasAlias ()); + assertNull (aParams.getCertificate ()); + assertNull (aParams.getAlias ()); + assertNull (aParams.getSecurityProviderEncrypt ()); + assertNull (aParams.getSecurityProviderDecrypt ()); + assertTrue (aParams.isEncryptSymmetricSessionKey ()); + assertFalse (aParams.hasWSSecEncryptCustomizer ()); + assertNull (aParams.getWSSecEncryptCustomizer ()); + } + + @Test + public void testCryptNotEnabledWithoutAlgorithm () + { + final AS4CryptParams aParams = new AS4CryptParams (); + assertFalse (aParams.isCryptEnabled (null)); + } + + @Test + public void testCryptNotEnabledWithoutCertOrAlias () + { + final AS4CryptParams aParams = new AS4CryptParams (); + aParams.setAlgorithmCrypt (ECryptoAlgorithmCrypt.AES_128_GCM); + // Algorithm set, but no certificate or alias + assertFalse (aParams.isCryptEnabled (null)); + } + + @Test + public void testCryptEnabledWithAlias () + { + final AS4CryptParams aParams = new AS4CryptParams (); + aParams.setAlgorithmCrypt (ECryptoAlgorithmCrypt.AES_128_GCM) + .setAlias ("test-alias"); + assertTrue (aParams.isCryptEnabled (null)); + assertTrue (aParams.hasAlias ()); + assertEquals ("test-alias", aParams.getAlias ()); } @Test @@ -69,6 +107,38 @@ public void testEDelivery2ECDHES () assertEquals (ECryptoKeyWrapAlgorithm.AES_128, aParams.getKeyWrapAlgorithm ()); } + @Test + public void testResetKeyAgreement () + { + final AS4CryptParams aParams = new AS4CryptParams (); + aParams.setEDelivery2KeyAgreementX25519 (); + assertTrue (aParams.hasKeyAgreementMethod ()); + + // Reset key agreement to null (back to key transport mode) + aParams.setKeyAgreementMethod (null) + .setKeyDerivationMethod (null) + .setKeyWrapAlgorithm (null); + assertFalse (aParams.hasKeyAgreementMethod ()); + assertNull (aParams.getKeyAgreementMethod ()); + assertNull (aParams.getKeyDerivationMethod ()); + assertNull (aParams.getKeyWrapAlgorithm ()); + } + + @Test + public void testIndividualKeyAgreementSetters () + { + final AS4CryptParams aParams = new AS4CryptParams (); + + aParams.setKeyAgreementMethod (ECryptoKeyAgreementMethod.X448); + assertSame (ECryptoKeyAgreementMethod.X448, aParams.getKeyAgreementMethod ()); + + aParams.setKeyDerivationMethod (ECryptoKeyDerivationMethod.CONCAT_KDF); + assertSame (ECryptoKeyDerivationMethod.CONCAT_KDF, aParams.getKeyDerivationMethod ()); + + aParams.setKeyWrapAlgorithm (ECryptoKeyWrapAlgorithm.AES_256); + assertSame (ECryptoKeyWrapAlgorithm.AES_256, aParams.getKeyWrapAlgorithm ()); + } + @Test public void testCloneWithKeyAgreement () { @@ -83,4 +153,53 @@ public void testCloneWithKeyAgreement () assertEquals (ECryptoKeyWrapAlgorithm.AES_128, aClone.getKeyWrapAlgorithm ()); assertEquals (ECryptoAlgorithmCrypt.AES_128_GCM, aClone.getAlgorithmCrypt ()); } + + @Test + public void testCloneWithoutKeyAgreement () + { + final AS4CryptParams aParams = new AS4CryptParams (); + aParams.setAlgorithmCrypt (ECryptoAlgorithmCrypt.AES_256_GCM) + .setKeyEncAlgorithm (ECryptoKeyEncryptionAlgorithm.RSA_OAEP) + .setAlias ("my-alias") + .setEncryptSymmetricSessionKey (false); + + final AS4CryptParams aClone = aParams.getClone (); + assertNotNull (aClone); + assertNull (aClone.getKeyAgreementMethod ()); + assertEquals (ECryptoAlgorithmCrypt.AES_256_GCM, aClone.getAlgorithmCrypt ()); + assertEquals (ECryptoKeyEncryptionAlgorithm.RSA_OAEP, aClone.getKeyEncAlgorithm ()); + assertEquals ("my-alias", aClone.getAlias ()); + assertFalse (aClone.isEncryptSymmetricSessionKey ()); + } + + @Test + public void testCreateDefault () + { + final AS4CryptParams aParams = AS4CryptParams.createDefault (); + assertNotNull (aParams); + assertEquals (ECryptoAlgorithmCrypt.AES_128_GCM, aParams.getAlgorithmCrypt ()); + } + + @Test + public void testToString () + { + final AS4CryptParams aParams = new AS4CryptParams (); + aParams.setAlgorithmCrypt (ECryptoAlgorithmCrypt.AES_128_GCM) + .setEDelivery2KeyAgreementX25519 (); + final String sToString = aParams.toString (); + assertNotNull (sToString); + assertTrue (sToString.contains ("KeyAgreementMethod")); + assertTrue (sToString.contains ("KeyDerivationMethod")); + assertTrue (sToString.contains ("KeyWrapAlgorithm")); + } + + @Test + public void testToStringWithoutKeyAgreement () + { + final AS4CryptParams aParams = new AS4CryptParams (); + final String sToString = aParams.toString (); + assertNotNull (sToString); + // Key agreement fields should not appear when null + assertFalse (sToString.contains ("KeyAgreementMethod")); + } } diff --git a/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyAgreementMethodTest.java b/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyAgreementMethodTest.java index 0de4e613d..6d93ffeb5 100644 --- a/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyAgreementMethodTest.java +++ b/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyAgreementMethodTest.java @@ -16,9 +16,12 @@ */ package com.helger.phase4.crypto; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import org.apache.wss4j.common.WSS4JConstants; import org.junit.Test; import com.helger.base.string.StringHelper; @@ -40,4 +43,28 @@ public void testBasic () assertSame (e, ECryptoKeyAgreementMethod.getFromIDOrDefault (e.getID (), null)); } } + + @Test + public void testUnknownID () + { + assertNull (ECryptoKeyAgreementMethod.getFromIDOrNull ("does-not-exist")); + assertNull (ECryptoKeyAgreementMethod.getFromIDOrNull (null)); + assertNull (ECryptoKeyAgreementMethod.getFromIDOrNull ("")); + assertSame (ECryptoKeyAgreementMethod.X25519, + ECryptoKeyAgreementMethod.getFromIDOrDefault ("does-not-exist", ECryptoKeyAgreementMethod.X25519)); + } + + @Test + public void testWSS4JConstantsMatch () + { + assertEquals (WSS4JConstants.AGREEMENT_METHOD_ECDH_ES, ECryptoKeyAgreementMethod.ECDH_ES.getID ()); + assertEquals (WSS4JConstants.AGREEMENT_METHOD_X25519, ECryptoKeyAgreementMethod.X25519.getID ()); + assertEquals (WSS4JConstants.AGREEMENT_METHOD_X448, ECryptoKeyAgreementMethod.X448.getID ()); + } + + @Test + public void testExpectedCount () + { + assertEquals (3, ECryptoKeyAgreementMethod.values ().length); + } } diff --git a/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyDerivationMethodTest.java b/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyDerivationMethodTest.java index 189a194ce..dfb23e6b9 100644 --- a/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyDerivationMethodTest.java +++ b/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyDerivationMethodTest.java @@ -16,9 +16,12 @@ */ package com.helger.phase4.crypto; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import org.apache.wss4j.common.WSS4JConstants; import org.junit.Test; import com.helger.base.string.StringHelper; @@ -40,4 +43,27 @@ public void testBasic () assertSame (e, ECryptoKeyDerivationMethod.getFromIDOrDefault (e.getID (), null)); } } + + @Test + public void testUnknownID () + { + assertNull (ECryptoKeyDerivationMethod.getFromIDOrNull ("does-not-exist")); + assertNull (ECryptoKeyDerivationMethod.getFromIDOrNull (null)); + assertNull (ECryptoKeyDerivationMethod.getFromIDOrNull ("")); + assertSame (ECryptoKeyDerivationMethod.HKDF, + ECryptoKeyDerivationMethod.getFromIDOrDefault ("does-not-exist", ECryptoKeyDerivationMethod.HKDF)); + } + + @Test + public void testWSS4JConstantsMatch () + { + assertEquals (WSS4JConstants.KEYDERIVATION_CONCATKDF, ECryptoKeyDerivationMethod.CONCAT_KDF.getID ()); + assertEquals (WSS4JConstants.KEYDERIVATION_HKDF, ECryptoKeyDerivationMethod.HKDF.getID ()); + } + + @Test + public void testExpectedCount () + { + assertEquals (2, ECryptoKeyDerivationMethod.values ().length); + } } diff --git a/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyEncryptionAlgorithmTest.java b/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyEncryptionAlgorithmTest.java new file mode 100644 index 000000000..e74c37911 --- /dev/null +++ b/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyEncryptionAlgorithmTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015-2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.crypto; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.helger.base.string.StringHelper; + +/** + * Test class for class {@link ECryptoKeyEncryptionAlgorithm}. + * + * @author Philip Helger + */ +public final class ECryptoKeyEncryptionAlgorithmTest +{ + @Test + public void testBasic () + { + for (final ECryptoKeyEncryptionAlgorithm e : ECryptoKeyEncryptionAlgorithm.values ()) + { + assertTrue (StringHelper.isNotEmpty (e.getID ())); + assertSame (e, ECryptoKeyEncryptionAlgorithm.getFromIDOrNull (e.getID ())); + assertSame (e, ECryptoKeyEncryptionAlgorithm.getFromIDOrDefault (e.getID (), null)); + } + } + + @Test + public void testUnknownID () + { + assertNull (ECryptoKeyEncryptionAlgorithm.getFromIDOrNull ("does-not-exist")); + assertNull (ECryptoKeyEncryptionAlgorithm.getFromIDOrNull (null)); + assertNull (ECryptoKeyEncryptionAlgorithm.getFromIDOrNull ("")); + assertSame (ECryptoKeyEncryptionAlgorithm.RSA_OAEP, + ECryptoKeyEncryptionAlgorithm.getFromIDOrDefault ("does-not-exist", + ECryptoKeyEncryptionAlgorithm.RSA_OAEP)); + } +} diff --git a/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyIdentifierTypeTest.java b/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyIdentifierTypeTest.java new file mode 100644 index 000000000..e3699b36c --- /dev/null +++ b/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyIdentifierTypeTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015-2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.crypto; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.helger.base.string.StringHelper; + +/** + * Test class for class {@link ECryptoKeyIdentifierType}. + * + * @author Philip Helger + */ +public final class ECryptoKeyIdentifierTypeTest +{ + @Test + public void testBasic () + { + for (final ECryptoKeyIdentifierType e : ECryptoKeyIdentifierType.values ()) + { + assertTrue (StringHelper.isNotEmpty (e.getID ())); + assertSame (e, ECryptoKeyIdentifierType.getFromIDOrNull (e.getID ())); + assertSame (e, ECryptoKeyIdentifierType.getFromIDOrDefault (e.getID (), null)); + assertSame (e, ECryptoKeyIdentifierType.getFromIDOrThrow (e.getID ())); + } + } + + @Test + public void testTypeIDRoundTrip () + { + for (final ECryptoKeyIdentifierType e : ECryptoKeyIdentifierType.values ()) + { + final ECryptoKeyIdentifierType eFound = ECryptoKeyIdentifierType.getFromTypeIDOrNull (e.getTypeID ()); + assertNotNull ("Failed to find by typeID for " + e, eFound); + assertSame (e, eFound); + } + } + + @Test + public void testUnknownID () + { + assertNull (ECryptoKeyIdentifierType.getFromIDOrNull ("does-not-exist")); + assertNull (ECryptoKeyIdentifierType.getFromIDOrNull (null)); + assertNull (ECryptoKeyIdentifierType.getFromIDOrNull ("")); + assertSame (ECryptoKeyIdentifierType.BST_DIRECT_REFERENCE, + ECryptoKeyIdentifierType.getFromIDOrDefault ("does-not-exist", + ECryptoKeyIdentifierType.BST_DIRECT_REFERENCE)); + } + + @Test + public void testUnknownTypeID () + { + assertNull (ECryptoKeyIdentifierType.getFromTypeIDOrNull (-999)); + } +} diff --git a/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyWrapAlgorithmTest.java b/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyWrapAlgorithmTest.java index 259cae7cb..31594e029 100644 --- a/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyWrapAlgorithmTest.java +++ b/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoKeyWrapAlgorithmTest.java @@ -16,9 +16,12 @@ */ package com.helger.phase4.crypto; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import org.apache.wss4j.common.WSS4JConstants; import org.junit.Test; import com.helger.base.string.StringHelper; @@ -40,4 +43,29 @@ public void testBasic () assertSame (e, ECryptoKeyWrapAlgorithm.getFromIDOrDefault (e.getID (), null)); } } + + @Test + public void testUnknownID () + { + assertNull (ECryptoKeyWrapAlgorithm.getFromIDOrNull ("does-not-exist")); + assertNull (ECryptoKeyWrapAlgorithm.getFromIDOrNull (null)); + assertNull (ECryptoKeyWrapAlgorithm.getFromIDOrNull ("")); + assertSame (ECryptoKeyWrapAlgorithm.AES_128, + ECryptoKeyWrapAlgorithm.getFromIDOrDefault ("does-not-exist", ECryptoKeyWrapAlgorithm.AES_128)); + } + + @Test + public void testWSS4JConstantsMatch () + { + assertEquals (WSS4JConstants.KEYWRAP_AES128, ECryptoKeyWrapAlgorithm.AES_128.getID ()); + assertEquals (WSS4JConstants.KEYWRAP_AES192, ECryptoKeyWrapAlgorithm.AES_192.getID ()); + assertEquals (WSS4JConstants.KEYWRAP_AES256, ECryptoKeyWrapAlgorithm.AES_256.getID ()); + assertEquals (WSS4JConstants.KEYWRAP_TRIPLEDES, ECryptoKeyWrapAlgorithm.TRIPLE_DES.getID ()); + } + + @Test + public void testExpectedCount () + { + assertEquals (4, ECryptoKeyWrapAlgorithm.values ().length); + } } diff --git a/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoModeTest.java b/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoModeTest.java new file mode 100644 index 000000000..c6918aca2 --- /dev/null +++ b/phase4-lib/src/test/java/com/helger/phase4/crypto/ECryptoModeTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015-2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.crypto; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; + +/** + * Test class for class {@link ECryptoMode}. + * + * @author Philip Helger + */ +public final class ECryptoModeTest +{ + @Test + public void testBasic () + { + assertEquals (2, ECryptoMode.values ().length); + assertNotNull (ECryptoMode.ENCRYPT_SIGN); + assertNotNull (ECryptoMode.DECRYPT_VERIFY); + assertNotNull (ECryptoMode.valueOf ("ENCRYPT_SIGN")); + assertNotNull (ECryptoMode.valueOf ("DECRYPT_VERIFY")); + } +} From 05a918768c0b8c027bf4d81c5515e686bdaeb493 Mon Sep 17 00:00:00 2001 From: Philip Helger Date: Thu, 19 Mar 2026 10:54:04 +0100 Subject: [PATCH 5/6] Added eDelivery v2 support --- .gitignore | 1 + phase4-edelivery2-client/pom.xml | 117 ++++ phase4-edelivery2-client/src/etc/javadoc.css | 583 ++++++++++++++++++ .../src/etc/license-template.txt | 14 + .../Phase4EDelivery2HttpClientSettings.java | 72 +++ .../edelivery2/Phase4EDelivery2Sender.java | 414 +++++++++++++ .../src/main/resources/LICENSE | 202 ++++++ .../src/main/resources/NOTICE | 5 + .../MainPhase4EDelivery2SendToValidator.java | 204 ++++++ .../com/helger/phase4/edelivery2/SPITest.java | 35 ++ .../phase4/incoming/AS4IncomingHandler.java | 77 +-- phase4-profile-edelivery2/pom.xml | 94 +++ phase4-profile-edelivery2/src/etc/javadoc.css | 583 ++++++++++++++++++ .../src/etc/license-template.txt | 14 + .../AS4EDelivery2ProfileRegistarSPI.java | 125 ++++ .../EDelivery2CompatibilityValidator.java | 479 ++++++++++++++ .../profile/edelivery2/EDelivery2PMode.java | 254 ++++++++ .../src/main/resources/LICENSE | 202 ++++++ ...ger.phase4.profile.IAS4ProfileRegistrarSPI | 1 + .../src/main/resources/NOTICE | 5 + .../EDelivery2CompatibilityValidatorTest.java | 111 ++++ .../phase4/profile/edelivery2/SPITest.java | 35 ++ pom.xml | 17 +- 23 files changed, 3605 insertions(+), 39 deletions(-) create mode 100644 phase4-edelivery2-client/pom.xml create mode 100644 phase4-edelivery2-client/src/etc/javadoc.css create mode 100644 phase4-edelivery2-client/src/etc/license-template.txt create mode 100644 phase4-edelivery2-client/src/main/java/com/helger/phase4/edelivery2/Phase4EDelivery2HttpClientSettings.java create mode 100644 phase4-edelivery2-client/src/main/java/com/helger/phase4/edelivery2/Phase4EDelivery2Sender.java create mode 100644 phase4-edelivery2-client/src/main/resources/LICENSE create mode 100644 phase4-edelivery2-client/src/main/resources/NOTICE create mode 100644 phase4-edelivery2-client/src/test/java/com/helger/phase4/edelivery2/MainPhase4EDelivery2SendToValidator.java create mode 100644 phase4-edelivery2-client/src/test/java/com/helger/phase4/edelivery2/SPITest.java create mode 100644 phase4-profile-edelivery2/pom.xml create mode 100644 phase4-profile-edelivery2/src/etc/javadoc.css create mode 100644 phase4-profile-edelivery2/src/etc/license-template.txt create mode 100644 phase4-profile-edelivery2/src/main/java/com/helger/phase4/profile/edelivery2/AS4EDelivery2ProfileRegistarSPI.java create mode 100644 phase4-profile-edelivery2/src/main/java/com/helger/phase4/profile/edelivery2/EDelivery2CompatibilityValidator.java create mode 100644 phase4-profile-edelivery2/src/main/java/com/helger/phase4/profile/edelivery2/EDelivery2PMode.java create mode 100644 phase4-profile-edelivery2/src/main/resources/LICENSE create mode 100644 phase4-profile-edelivery2/src/main/resources/META-INF/services/com.helger.phase4.profile.IAS4ProfileRegistrarSPI create mode 100644 phase4-profile-edelivery2/src/main/resources/NOTICE create mode 100644 phase4-profile-edelivery2/src/test/java/com/helger/phase4/profile/edelivery2/EDelivery2CompatibilityValidatorTest.java create mode 100644 phase4-profile-edelivery2/src/test/java/com/helger/phase4/profile/edelivery2/SPITest.java diff --git a/.gitignore b/.gitignore index 5b33414c7..6f95bf006 100644 --- a/.gitignore +++ b/.gitignore @@ -24,5 +24,6 @@ Main*ReceiverProd.java target/ generated/ phase4-data/ +phase4-dumps/ .apt_generated/ .apt_generated_tests/ diff --git a/phase4-edelivery2-client/pom.xml b/phase4-edelivery2-client/pom.xml new file mode 100644 index 000000000..70426d80d --- /dev/null +++ b/phase4-edelivery2-client/pom.xml @@ -0,0 +1,117 @@ + + + + 4.0.0 + + com.helger.phase4 + phase4-parent-pom + 4.3.3-SNAPSHOT + + phase4-edelivery2-client + bundle + phase4-edelivery2-client + eDelivery AS4 2.0 client for outgoing transmissions + https://github.com/phax/phase4/phase4-edelivery2-client + 2026 + + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0 + repo + + + + + + com.helger.phase4 + phase4-lib + + + com.helger.phase4 + phase4-profile-edelivery2 + ${project.version} + + + com.helger.phase4 + phase4-dynamic-discovery + + + com.helger.peppol + peppol-commons + ${peppol-commons.version} + + + com.helger.peppol + peppol-smp-client + ${peppol-commons.version} + + + dnsjava + dnsjava + ${dnsjava.version} + + + + jakarta.servlet + jakarta.servlet-api + provided + + + + com.sun.xml.bind + jaxb-impl + test + + + + junit + junit + test + + + org.slf4j + slf4j-simple + test + + + com.helger.commons + ph-unittest-support-ext + test + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + com.helger.phase4.edelivery2 + com.helger.phase4.edelivery2.* + !org.jspecify.annotations.*,* + + + + + + diff --git a/phase4-edelivery2-client/src/etc/javadoc.css b/phase4-edelivery2-client/src/etc/javadoc.css new file mode 100644 index 000000000..d739e4ac7 --- /dev/null +++ b/phase4-edelivery2-client/src/etc/javadoc.css @@ -0,0 +1,583 @@ +/** + * Copyright (C) 2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * based on phloc javadoc CSS. + * (c) 2011-2014 phloc systems. + * Derived from the original javadoc CSS from Sun JDK + */ + +body { + background-color: #FFFFFF; + color: #353833; + font-family: Arial, Helvetica, sans-serif; + font-size: 76%; + margin: 0; +} + +a:link,a:visited { + color: #880000; + text-decoration: none; +} + +a:hover,a:focus { + color: #BB2222; + text-decoration: none; +} + +a:active { + color: #4C6B87; + text-decoration: none; +} + +a[name] { + color: #353833; +} + +a[name]:hover { + color: #353833; + text-decoration: none; +} + +pre { + font-size: 1.3em; +} + +h1 { + font-size: 1.8em; +} + +h2 { + font-size: 1.5em; +} + +h3 { + font-size: 1.4em; +} + +h4 { + font-size: 1.3em; +} + +h5 { + font-size: 1.2em; +} + +h6 { + font-size: 1.1em; +} + +ul { + list-style-type: disc; +} + +code,tt { + font-size: 1.2em; +} + +dt code { + font-size: 1.2em; +} + +table tr td dt code { + font-size: 1.2em; + vertical-align: top; +} + +sup { + font-size: 0.6em; +} + +.clear { + clear: both; + height: 0; + overflow: hidden; +} + +.aboutLanguage { + float: right; + font-size: 0.8em; + margin-top: -7px; + padding: 0 21px; + z-index: 200; +} + +.legalCopy { + margin-left: 0.5em; +} + +.bar a,.bar a:link,.bar a:visited,.bar a:active { + color: #FFFFFF; + text-decoration: none; +} + +.bar a:hover,.bar a:focus { + color: #BB7A2A; +} + +.tab { + background-color: #0066FF; + background-image: url("resources/titlebar.gif"); + background-position: left top; + background-repeat: no-repeat; + color: #FFFFFF; + font-weight: bold; + padding: 8px; + width: 5em; +} + +.bar { + background-image: url("resources/background.gif"); + background-repeat: repeat-x; + color: #FFFFFF; + font-size: 1em; + height: auto; + margin: 0; + padding: 0.8em 0.5em 0.4em 0.8em; +} + +.topNav { + background-image: url("resources/background.gif"); + background-repeat: repeat-x; + clear: right; + color: #FFFFFF; + float: left; + height: 2.8em; + overflow: hidden; + padding: 10px 0 0; + width: 100%; +} + +.bottomNav { + background-image: url("resources/background.gif"); + background-repeat: repeat-x; + clear: right; + color: #FFFFFF; + float: left; + height: 2.8em; + margin-top: 10px; + overflow: hidden; + padding: 10px 0 0; + width: 100%; +} + +.subNav { + background-color: #DEE3E9; + border-bottom: 1px solid #9EADC0; + float: left; + overflow: hidden; + width: 100%; +} + +.subNav div { + clear: left; + float: left; + padding: 0 0 5px 6px; +} + +ul.navList,ul.subNavList { + float: left; + margin: 0 25px 0 0; + padding: 0; +} + +ul.navList li { + float: left; + list-style: none outside none; + padding: 3px 6px; +} + +ul.subNavList li { + float: left; + font-size: 90%; + list-style: none outside none; +} + +.topNav a:link,.topNav a:active,.topNav a:visited,.bottomNav a:link,.bottomNav a:active,.bottomNav a:visited + { + color: #FFFFFF; + text-decoration: none; +} + +.topNav a:hover,.bottomNav a:hover { + color: #BB7A2A; + text-decoration: none; +} + +.navBarCell1Rev { + background-color: #A88834; + background-image: url("resources/tab.gif"); + border: 1px solid #C9AA44; + color: #FFFFFF; + margin: auto 5px; +} + +.header,.footer { + clear: both; + margin: 0 20px; + padding: 5px 0 0; +} + +.indexHeader { + margin: 10px; + position: relative; +} + +.indexHeader h1 { + font-size: 1.3em; +} + +.title { + color: #880000; + margin: 10px 0; +} + +.subTitle { + margin: 5px 0 0; +} + +.header ul { + margin: 0 0 25px; + padding: 0; +} + +.footer ul { + margin: 20px 0 5px; +} + +.header ul li,.footer ul li { + font-size: 1.2em; + list-style: none outside none; +} + +div.details ul.blockList ul.blockList ul.blockList li.blockList h4,div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 + { + background-color: #DEE3E9; + border-bottom: 1px solid #9EADC0; + border-top: 1px solid #9EADC0; + margin: 0 0 6px -8px; + padding: 2px 5px; +} + +ul.blockList ul.blockList ul.blockList li.blockList h3 { + background-color: #DEE3E9; + border-bottom: 1px solid #9EADC0; + border-top: 1px solid #9EADC0; + margin: 0 0 6px -8px; + padding: 2px 5px; +} + +ul.blockList ul.blockList li.blockList h3 { + margin: 15px 0; + padding: 0; +} + +ul.blockList li.blockList h2 { + padding: 0 0 20px; +} + +.contentContainer,.sourceContainer,.classUseContainer,.serializedFormContainer,.constantValuesContainer + { + clear: both; + padding: 10px 20px; + position: relative; +} + +.indexContainer { + font-size: 1em; + margin: 10px; + position: relative; +} + +.indexContainer h2 { + font-size: 1.1em; + padding: 0 0 3px; +} + +.indexContainer ul { + margin: 0; + padding: 0; +} + +.indexContainer ul li { + list-style: none outside none; +} + +.contentContainer .description dl dt,.contentContainer .details dl dt,.serializedFormContainer dl dt + { + color: #4E4E4E; + font-size: 1.1em; + font-weight: bold; + margin: 10px 0 0; +} + +.contentContainer .description dl dd,.contentContainer .details dl dd,.serializedFormContainer dl dd + { + margin: 10px 0 10px 20px; +} + +.serializedFormContainer dl.nameValue dt { + display: inline; + font-size: 1.1em; + font-weight: bold; + margin-left: 1px; +} + +.serializedFormContainer dl.nameValue dd { + display: inline; + font-size: 1.1em; +} + +ul.horizontal li { + display: inline; + font-size: 0.9em; +} + +ul.inheritance { + margin: 0; + padding: 0; +} + +ul.inheritance li { + display: inline; + list-style: none outside none; +} + +ul.inheritance li ul.inheritance { + margin-left: 15px; + padding-left: 15px; + padding-top: 1px; +} + +ul.blockList,ul.blockListLast { + margin: 10px 0; + padding: 0; +} + +ul.blockList li.blockList,ul.blockListLast li.blockList { + list-style: none outside none; + margin-bottom: 25px; +} + +ul.blockList ul.blockList li.blockList,ul.blockList ul.blockListLast li.blockList + { + background-color: #F9F9F9; + border: 1px solid #9EADC0; + padding: 0 20px 5px 10px; +} + +ul.blockList ul.blockList ul.blockList li.blockList,ul.blockList ul.blockList ul.blockListLast li.blockList + { + -moz-border-bottom-colors: none; + -moz-border-left-colors: none; + -moz-border-right-colors: none; + -moz-border-top-colors: none; + background-color: #FFFFFF; + border-color: currentColor #9EADC0 #9EADC0; + border-image: none; + border-right: 1px solid #9EADC0; + border-style: none solid solid; + border-width: medium 1px 1px; + padding: 0 0 5px 8px; +} + +ul.blockList ul.blockList ul.blockList ul.blockList li.blockList { + -moz-border-bottom-colors: none; + -moz-border-left-colors: none; + -moz-border-right-colors: none; + -moz-border-top-colors: none; + border-color: currentColor currentColor #9EADC0; + border-image: none; + border-style: none none solid; + border-width: medium medium 1px; + margin-left: 0; + padding-bottom: 15px; + padding-left: 0; +} + +ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast { + border-bottom: medium none; + list-style: none outside none; + padding-bottom: 0; +} + +table tr td dl,table tr td dl dt,table tr td dl dd { + margin-bottom: 1px; + margin-top: 0; +} + +.contentContainer table,.classUseContainer table,.constantValuesContainer table + { + border-bottom: 1px solid #9EADC0; + width: 100%; +} + +.contentContainer ul li table,.classUseContainer ul li table,.constantValuesContainer ul li table + { + width: 100%; +} + +.contentContainer .description table,.contentContainer .details table { + border-bottom: medium none; +} + +.contentContainer ul li table th.colOne,.contentContainer ul li table th.colFirst,.contentContainer ul li table th.colLast,.classUseContainer ul li table th,.constantValuesContainer ul li table th,.contentContainer ul li table td.colOne,.contentContainer ul li table td.colFirst,.contentContainer ul li table td.colLast,.classUseContainer ul li table td,.constantValuesContainer ul li table td + { + padding-right: 20px; + vertical-align: top; +} + +.contentContainer ul li table th.colLast,.classUseContainer ul li table th.colLast,.constantValuesContainer ul li table th.colLast,.contentContainer ul li table td.colLast,.classUseContainer ul li table td.colLast,.constantValuesContainer ul li table td.colLast,.contentContainer ul li table th.colOne,.classUseContainer ul li table th.colOne,.contentContainer ul li table td.colOne,.classUseContainer ul li table td.colOne + { + padding-right: 3px; +} + +.overviewSummary caption,.packageSummary caption,.contentContainer ul.blockList li.blockList caption,.summary caption,.classUseContainer caption,.constantValuesContainer caption + { + background-repeat: no-repeat; + clear: none; + color: #FFFFFF; + font-weight: bold; + margin: 0; + overflow: hidden; + padding: 0; + position: relative; + text-align: left; +} + +caption a:link,caption a:hover,caption a:active,caption a:visited { + color: #FFFFFF; +} + +.overviewSummary caption span,.packageSummary caption span,.contentContainer ul.blockList li.blockList caption span,.summary caption span,.classUseContainer caption span,.constantValuesContainer caption span + { + background-image: url("resources/titlebar.gif"); + display: block; + float: left; + height: 18px; + padding-left: 8px; + padding-top: 8px; + white-space: nowrap; +} + +.overviewSummary .tabEnd,.packageSummary .tabEnd,.contentContainer ul.blockList li.blockList .tabEnd,.summary .tabEnd,.classUseContainer .tabEnd,.constantValuesContainer .tabEnd + { + background-image: url("resources/titlebar_end.gif"); + background-position: right top; + background-repeat: no-repeat; + float: left; + position: relative; + width: 10px; +} + +ul.blockList ul.blockList li.blockList table { + margin: 0 0 12px; + width: 100%; +} + +.tableSubHeadingColor { + background-color: #EEEEFF; +} + +.altColor { + background-color: #EEEEEF; +} + +.rowColor { + background-color: #FFFFFF; +} + +.overviewSummary td,.packageSummary td,.contentContainer ul.blockList li.blockList td,.summary td,.classUseContainer td,.constantValuesContainer td + { + padding: 3px 3px 3px 7px; + text-align: left; +} + +th.colFirst,th.colLast,th.colOne,.constantValuesContainer th { + background: none repeat scroll 0 0 #DEE3E9; + border-bottom: 1px solid #9EADC0; + border-top: 1px solid #9EADC0; + padding: 3px 3px 3px 7px; + text-align: left; +} + +td.colOne a:link,td.colOne a:active,td.colOne a:visited,td.colOne a:hover,td.colFirst a:link,td.colFirst a:active,td.colFirst a:visited,td.colFirst a:hover,td.colLast a:link,td.colLast a:active,td.colLast a:visited,td.colLast a:hover,.constantValuesContainer td a:link,.constantValuesContainer td a:active,.constantValuesContainer td a:visited,.constantValuesContainer td a:hover + { + font-weight: bold; +} + +td.colFirst,th.colFirst { + border-left: 1px solid #9EADC0; + white-space: nowrap; +} + +td.colLast,th.colLast { + border-right: 1px solid #9EADC0; +} + +td.colOne,th.colOne { + border-left: 1px solid #9EADC0; + border-right: 1px solid #9EADC0; +} + +table.overviewSummary { + margin-left: 0; + padding: 0; +} + +table.overviewSummary td.colFirst,table.overviewSummary th.colFirst,table.overviewSummary td.colOne,table.overviewSummary th.colOne + { + vertical-align: middle; + width: 25%; +} + +table.packageSummary td.colFirst,table.overviewSummary th.colFirst { + vertical-align: middle; + width: 25%; +} + +.description pre { + margin-top: 0; +} + +.deprecatedContent { + margin: 0; + padding: 10px 0; +} + +.docSummary { + padding: 0; +} + +.sourceLineNo { + color: #008000; + padding: 0 30px 0 0; +} + +h1.hidden { + font-size: 0.9em; + overflow: hidden; + visibility: hidden; +} + +.block { + display: block; + margin: 3px 0 0; +} + +.strong { + font-weight: bold; +} diff --git a/phase4-edelivery2-client/src/etc/license-template.txt b/phase4-edelivery2-client/src/etc/license-template.txt new file mode 100644 index 000000000..334a66dac --- /dev/null +++ b/phase4-edelivery2-client/src/etc/license-template.txt @@ -0,0 +1,14 @@ +Copyright (C) 2026 Philip Helger (www.helger.com) +philip[at]helger[dot]com + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/phase4-edelivery2-client/src/main/java/com/helger/phase4/edelivery2/Phase4EDelivery2HttpClientSettings.java b/phase4-edelivery2-client/src/main/java/com/helger/phase4/edelivery2/Phase4EDelivery2HttpClientSettings.java new file mode 100644 index 000000000..97b3970a2 --- /dev/null +++ b/phase4-edelivery2-client/src/main/java/com/helger/phase4/edelivery2/Phase4EDelivery2HttpClientSettings.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.edelivery2; + +import java.security.GeneralSecurityException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +import org.apache.hc.core5.util.Timeout; + +import com.helger.http.security.TrustManagerTrustAll; +import com.helger.http.tls.ETLSVersion; +import com.helger.httpclient.HttpClientSettings; +import com.helger.phase4.CAS4; +import com.helger.phase4.CAS4Version; + +/** + * Special {@link HttpClientSettings} with better defaults for eDelivery AS4 2.0. TLS 1.2 is the + * minimum required version; TLS 1.3 is preferred. + * + * @author Philip Helger + * @since 4.4.0 + */ +public class Phase4EDelivery2HttpClientSettings extends HttpClientSettings +{ + @SuppressWarnings ("hiding") + public static final Timeout DEFAULT_CONNECTION_REQUEST_TIMEOUT = Timeout.ofSeconds (1); + @SuppressWarnings ("hiding") + public static final Timeout DEFAULT_CONNECT_TIMEOUT = Timeout.ofSeconds (5); + @SuppressWarnings ("hiding") + public static final Timeout DEFAULT_RESPONSE_TIMEOUT = Timeout.ofSeconds (100); + + public Phase4EDelivery2HttpClientSettings () throws GeneralSecurityException + { + // eDelivery AS4 2.0 requires TLS v1.2 minimum, SHOULD support TLS v1.3 + // Try TLS 1.3 first, fall back to TLS 1.2 + SSLContext aSSLContext; + try + { + aSSLContext = SSLContext.getInstance (ETLSVersion.TLS_13.getID ()); + } + catch (final GeneralSecurityException ex) + { + // TLS 1.3 not available, fall back to TLS 1.2 + aSSLContext = SSLContext.getInstance (ETLSVersion.TLS_12.getID ()); + } + aSSLContext.init (null, new TrustManager [] { new TrustManagerTrustAll (false) }, null); + setSSLContext (aSSLContext); + + setConnectionRequestTimeout (DEFAULT_CONNECTION_REQUEST_TIMEOUT); + setConnectTimeout (DEFAULT_CONNECT_TIMEOUT); + setResponseTimeout (DEFAULT_RESPONSE_TIMEOUT); + + // Set an explicit user agent + setUserAgent (CAS4.LIB_NAME + "/" + CAS4Version.BUILD_VERSION + " " + CAS4.LIB_URL); + } +} diff --git a/phase4-edelivery2-client/src/main/java/com/helger/phase4/edelivery2/Phase4EDelivery2Sender.java b/phase4-edelivery2-client/src/main/java/com/helger/phase4/edelivery2/Phase4EDelivery2Sender.java new file mode 100644 index 000000000..cefddf084 --- /dev/null +++ b/phase4-edelivery2-client/src/main/java/com/helger/phase4/edelivery2/Phase4EDelivery2Sender.java @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.edelivery2; + +import java.security.cert.X509Certificate; +import java.util.function.Consumer; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.slf4j.Logger; + +import com.helger.annotation.Nonempty; +import com.helger.annotation.OverridingMethodsMustInvokeSuper; +import com.helger.annotation.concurrent.Immutable; +import com.helger.annotation.concurrent.NotThreadSafe; +import com.helger.base.state.ESuccess; +import com.helger.base.string.StringHelper; +import com.helger.peppolid.IDocumentTypeIdentifier; +import com.helger.peppolid.IParticipantIdentifier; +import com.helger.peppolid.IProcessIdentifier; +import com.helger.peppolid.factory.SimpleIdentifierFactory; +import com.helger.phase4.CAS4; +import com.helger.phase4.crypto.ECryptoAlgorithmSign; +import com.helger.phase4.dynamicdiscovery.AS4EndpointDetailProviderBDXR; +import com.helger.phase4.dynamicdiscovery.AS4EndpointDetailProviderBDXR2; +import com.helger.phase4.dynamicdiscovery.AS4EndpointDetailProviderConstant; +import com.helger.phase4.dynamicdiscovery.IAS4EndpointDetailProvider; +import com.helger.phase4.logging.Phase4LoggerFactory; +import com.helger.phase4.model.MessageProperty; +import com.helger.phase4.profile.edelivery2.AS4EDelivery2ProfileRegistarSPI; +import com.helger.phase4.sender.AbstractAS4UserMessageBuilderMIMEPayload; +import com.helger.phase4.util.AS4ResourceHelper; +import com.helger.phase4.util.Phase4Exception; +import com.helger.smpclient.bdxr1.IBDXRExtendedServiceMetadataProvider; +import com.helger.smpclient.bdxr2.IBDXR2ServiceMetadataProvider; +import com.helger.smpclient.url.BDXLURLProvider; +import com.helger.smpclient.url.IBDXLURLProvider; + +/** + * This class contains all the specifics to send AS4 messages with the eDelivery AS4 2.0 profile. + * See sendAS4Message as the main method to trigger the sending, with all potential + * customization.
+ * Supports both the Common Usage Profile (EdDSA/X25519) and the Alternative Elliptic Curve Profile + * (ECDSA/ECDH-ES with secp256r1). + * + * @author Philip Helger + * @since 4.4.0 + */ +@Immutable +public final class Phase4EDelivery2Sender +{ + public static final SimpleIdentifierFactory IF = SimpleIdentifierFactory.INSTANCE; + public static final IBDXLURLProvider URL_PROVIDER = BDXLURLProvider.INSTANCE; + + private static final Logger LOGGER = Phase4LoggerFactory.getLogger (Phase4EDelivery2Sender.class); + + private Phase4EDelivery2Sender () + {} + + /** + * @return Create a new Builder for AS4 messages using the Common Usage Profile (EdDSA/X25519). + * Never null. + */ + @NonNull + public static EDelivery2EdDSAUserMessageBuilder builderEdDSA () + { + return new EDelivery2EdDSAUserMessageBuilder (); + } + + /** + * @return Create a new Builder for AS4 messages using the Alternative EC Profile (ECDSA/ECDH-ES). + * Never null. + */ + @NonNull + public static EDelivery2ECDSAUserMessageBuilder builderECDSA () + { + return new EDelivery2ECDSAUserMessageBuilder (); + } + + /** + * Abstract eDelivery 2.0 UserMessage builder class with sanity methods + * + * @author Philip Helger + * @param + * The implementation type + */ + @NotThreadSafe + public abstract static class AbstractEDelivery2UserMessageBuilder > + extends + AbstractAS4UserMessageBuilderMIMEPayload + { + public static final boolean DEFAULT_USE_ORIGINAL_SENDER_FINAL_RECIPIENT_TYPE_ATTR = true; + + protected IParticipantIdentifier m_aSenderID; + protected IParticipantIdentifier m_aReceiverID; + protected IDocumentTypeIdentifier m_aDocTypeID; + protected IProcessIdentifier m_aProcessID; + protected IAS4EndpointDetailProvider m_aEndpointDetailProvider; + protected Consumer m_aCertificateConsumer; + protected Consumer m_aAPEndointURLConsumer; + protected boolean m_bUseOriginalSenderFinalRecipientTypeAttr = DEFAULT_USE_ORIGINAL_SENDER_FINAL_RECIPIENT_TYPE_ATTR; + + protected AbstractEDelivery2UserMessageBuilder (@NonNull final String sProfileID) + { + try + { + as4ProfileID (sProfileID); + httpClientFactory (new Phase4EDelivery2HttpClientSettings ()); + } + catch (final Exception ex) + { + throw new IllegalStateException ("Failed to init AS4 Client builder", ex); + } + } + + /** + * Set the sender participant ID of the message. The participant ID must be provided prior to + * sending. This ends up in the "originalSender" UserMessage property. + * + * @param a + * The sender participant ID. May not be null. + * @return this for chaining + */ + @NonNull + public final IMPLTYPE senderParticipantID (@Nullable final IParticipantIdentifier a) + { + m_aSenderID = a; + return thisAsT (); + } + + /** + * Set the receiver participant ID of the message. The participant ID must be provided prior to + * sending. This ends up in the "finalRecipient" UserMessage property. + * + * @param a + * The receiver participant ID. May not be null. + * @return this for chaining + */ + @NonNull + public final IMPLTYPE receiverParticipantID (@Nullable final IParticipantIdentifier a) + { + m_aReceiverID = a; + return thisAsT (); + } + + /** + * Set the document type ID to be send. The document type must be provided prior to sending. + * + * @param a + * The document type ID to be used. May not be null. + * @return this for chaining + */ + @NonNull + public final IMPLTYPE documentTypeID (@Nullable final IDocumentTypeIdentifier a) + { + m_aDocTypeID = a; + return action (a == null ? null : a.getURIEncoded ()); + } + + /** + * Set the process ID to be send. The process ID must be provided prior to sending. + * + * @param a + * The process ID to be used. May not be null. + * @return this for chaining + */ + @NonNull + public final IMPLTYPE processID (@Nullable final IProcessIdentifier a) + { + m_aProcessID = a; + return service (a == null ? null : a.getScheme (), a == null ? null : a.getValue ()); + } + + /** + * Set the "from party ID". This is mandatory + * + * @param a + * The from party ID. May be null. + * @return this for chaining + */ + @NonNull + public final IMPLTYPE fromPartyID (@Nullable final IParticipantIdentifier a) + { + return fromPartyIDType (a == null ? null : a.getScheme ()).fromPartyID (a == null ? null : a.getValue ()); + } + + /** + * Set the "to party ID". This is mandatory + * + * @param a + * The to party ID. May be null. + * @return this for chaining + */ + @NonNull + public final IMPLTYPE toPartyID (@Nullable final IParticipantIdentifier a) + { + return toPartyIDType (a == null ? null : a.getScheme ()).toPartyID (a == null ? null : a.getValue ()); + } + + @NonNull + public final IMPLTYPE endpointDetailProvider (@Nullable final IAS4EndpointDetailProvider a) + { + m_aEndpointDetailProvider = a; + return thisAsT (); + } + + @NonNull + public final IMPLTYPE smpClient (@NonNull final IBDXRExtendedServiceMetadataProvider a) + { + return endpointDetailProvider (new AS4EndpointDetailProviderBDXR (a)); + } + + @NonNull + public final IMPLTYPE smpClient (@NonNull final IBDXR2ServiceMetadataProvider a) + { + return endpointDetailProvider (new AS4EndpointDetailProviderBDXR2 (a)); + } + + @NonNull + public final IMPLTYPE receiverEndpointDetails (@NonNull final X509Certificate aCert, + @NonNull @Nonempty final String sDestURL) + { + return endpointDetailProvider (new AS4EndpointDetailProviderConstant (aCert, sDestURL)); + } + + @NonNull + public final IMPLTYPE certificateConsumer (@Nullable final Consumer a) + { + m_aCertificateConsumer = a; + return thisAsT (); + } + + @NonNull + public final IMPLTYPE endointURLConsumer (@Nullable final Consumer a) + { + m_aAPEndointURLConsumer = a; + return thisAsT (); + } + + @NonNull + public final IMPLTYPE useOriginalSenderFinalRecipientTypeAttr (final boolean b) + { + m_bUseOriginalSenderFinalRecipientTypeAttr = b; + return thisAsT (); + } + + protected final boolean isEndpointDetailProviderUsable () + { + if (m_aEndpointDetailProvider instanceof AS4EndpointDetailProviderConstant) + return true; + + if (m_aReceiverID == null) + return false; + if (m_aDocTypeID == null) + return false; + if (m_aProcessID == null) + return false; + if (m_aEndpointDetailProvider == null) + return false; + + return true; + } + + @Override + @OverridingMethodsMustInvokeSuper + protected ESuccess finishFields (@NonNull final AS4ResourceHelper aResHelper) throws Phase4Exception + { + if (!isEndpointDetailProviderUsable ()) + { + LOGGER.error ("At least one mandatory field for endpoint discovery is not set and therefore the AS4 message cannot be send."); + return ESuccess.FAILURE; + } + + m_aEndpointDetailProvider.init (m_aDocTypeID, m_aProcessID, m_aReceiverID); + + final X509Certificate aReceiverCert = m_aEndpointDetailProvider.getReceiverAPCertificate (); + if (m_aCertificateConsumer != null) + m_aCertificateConsumer.accept (aReceiverCert); + receiverCertificate (aReceiverCert); + + final String sReceiverEndpointURL = m_aEndpointDetailProvider.getReceiverAPEndpointURL (); + if (m_aAPEndointURLConsumer != null) + m_aAPEndointURLConsumer.accept (sReceiverEndpointURL); + endpointURL (sReceiverEndpointURL); + + return super.finishFields (aResHelper); + } + + @Override + @OverridingMethodsMustInvokeSuper + public boolean isEveryRequiredFieldSet () + { + if (!super.isEveryRequiredFieldSet ()) + return false; + + if (m_aPayload == null) + { + LOGGER.warn ("The field 'payload' is not set"); + return false; + } + if (m_aSenderID == null) + { + LOGGER.warn ("The field 'senderID' is not set"); + return false; + } + if (m_aReceiverID == null) + { + LOGGER.warn ("The field 'receiverID' is not set"); + return false; + } + if (m_aDocTypeID == null && StringHelper.isEmpty (m_sAction)) + { + LOGGER.warn ("Neither the field 'docTypeID' nor the field 'action' is set"); + return false; + } + if (m_aProcessID == null && StringHelper.isEmpty (m_sService)) + { + LOGGER.warn ("Neither the field 'processID' nor the field 'service' is set"); + return false; + } + if (m_aEndpointDetailProvider == null) + { + LOGGER.warn ("The field 'endpointDetailProvider' is not set"); + return false; + } + + return true; + } + + @Override + protected void customizeBeforeSending () throws Phase4Exception + { + // Add mandatory properties + if (m_bUseOriginalSenderFinalRecipientTypeAttr) + { + addMessageProperty (MessageProperty.builder () + .name (CAS4.ORIGINAL_SENDER) + .type (m_aSenderID.getScheme ()) + .value (m_aSenderID.getValue ())); + addMessageProperty (MessageProperty.builder () + .name (CAS4.FINAL_RECIPIENT) + .type (m_aReceiverID.getScheme ()) + .value (m_aReceiverID.getValue ())); + } + else + { + addMessageProperty (MessageProperty.builder () + .name (CAS4.ORIGINAL_SENDER) + .value (m_aSenderID.getURIEncoded ())); + addMessageProperty (MessageProperty.builder () + .name (CAS4.FINAL_RECIPIENT) + .value (m_aReceiverID.getURIEncoded ())); + } + } + } + + /** + * The builder class for sending AS4 messages using the eDelivery AS4 2.0 Common Usage Profile + * (EdDSA/X25519). Use {@link #sendMessage()} or {@link #sendMessageAndCheckForReceipt()} to + * trigger the main transmission. + * + * @author Philip Helger + */ + @NotThreadSafe + public static class EDelivery2EdDSAUserMessageBuilder extends + AbstractEDelivery2UserMessageBuilder + { + public EDelivery2EdDSAUserMessageBuilder () + { + super (AS4EDelivery2ProfileRegistarSPI.AS4_PROFILE_ID_EDDSA_FOUR_CORNER); + + // Set default crypto params for EdDSA/X25519 + signingParams ().setAlgorithmSign (ECryptoAlgorithmSign.EDDSA_ED25519); + cryptParams ().setAlgorithmCrypt (com.helger.phase4.crypto.ECryptoAlgorithmCrypt.AES_128_GCM) + .setEDelivery2KeyAgreementX25519 (); + } + } + + /** + * The builder class for sending AS4 messages using the eDelivery AS4 2.0 Alternative EC Profile + * (ECDSA/ECDH-ES with secp256r1). Use {@link #sendMessage()} or + * {@link #sendMessageAndCheckForReceipt()} to trigger the main transmission. + * + * @author Philip Helger + */ + @NotThreadSafe + public static class EDelivery2ECDSAUserMessageBuilder extends + AbstractEDelivery2UserMessageBuilder + { + public EDelivery2ECDSAUserMessageBuilder () + { + super (AS4EDelivery2ProfileRegistarSPI.AS4_PROFILE_ID_ECDSA_FOUR_CORNER); + + // Set default crypto params for ECDSA/ECDH-ES + signingParams ().setAlgorithmSign (ECryptoAlgorithmSign.ECDSA_SHA_256); + cryptParams ().setAlgorithmCrypt (com.helger.phase4.crypto.ECryptoAlgorithmCrypt.AES_128_GCM) + .setEDelivery2KeyAgreementECDHES (); + } + } +} diff --git a/phase4-edelivery2-client/src/main/resources/LICENSE b/phase4-edelivery2-client/src/main/resources/LICENSE new file mode 100644 index 000000000..8f71f43fe --- /dev/null +++ b/phase4-edelivery2-client/src/main/resources/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/phase4-edelivery2-client/src/main/resources/NOTICE b/phase4-edelivery2-client/src/main/resources/NOTICE new file mode 100644 index 000000000..6b30bbfce --- /dev/null +++ b/phase4-edelivery2-client/src/main/resources/NOTICE @@ -0,0 +1,5 @@ +============================================================================= += NOTICE file corresponding to section 4d of the Apache License Version 2.0 = +============================================================================= +This product includes Open Source Software developed by +Philip Helger - https://www.helger.com/ diff --git a/phase4-edelivery2-client/src/test/java/com/helger/phase4/edelivery2/MainPhase4EDelivery2SendToValidator.java b/phase4-edelivery2-client/src/test/java/com/helger/phase4/edelivery2/MainPhase4EDelivery2SendToValidator.java new file mode 100644 index 000000000..68746475f --- /dev/null +++ b/phase4-edelivery2-client/src/test/java/com/helger/phase4/edelivery2/MainPhase4EDelivery2SendToValidator.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.edelivery2; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.security.KeyStore; +import java.security.Security; +import java.security.cert.X509Certificate; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.jspecify.annotations.NonNull; +import org.slf4j.Logger; + +import com.helger.phase4.CAS4; +import com.helger.phase4.attachment.AS4OutgoingAttachment; +import com.helger.phase4.client.IAS4ClientBuildMessageCallback; +import com.helger.phase4.crypto.AS4CryptoFactoryInMemoryKeyStore; +import com.helger.phase4.crypto.IAS4CryptoFactory; +import com.helger.phase4.dump.AS4DumpManager; +import com.helger.phase4.dump.AS4IncomingDumperFileBased; +import com.helger.phase4.dump.AS4OutgoingDumperFileBased; +import com.helger.phase4.logging.Phase4LoggerFactory; +import com.helger.phase4.model.message.AS4UserMessage; +import com.helger.phase4.model.message.AbstractAS4Message; +import com.helger.phase4.sender.EAS4UserMessageSendResult; +import com.helger.security.keystore.EKeyStoreType; +import com.helger.security.keystore.KeyStoreHelper; +import com.helger.servlet.mock.MockServletContext; +import com.helger.web.scope.mgr.WebScopeManager; +import com.helger.web.scope.mgr.WebScoped; + +/** + * Standalone test that sends an eDelivery AS4 2.0 message to the EC eDelivery2 AS4 Security + * Validator running locally on port 8080.
+ *
+ * Prerequisites: + *
    + *
  • The EC eDelivery2 AS4 Security Validator must be running locally (default port 8080)
  • + *
  • We use the validator's own "blue" keystore for signing/encryption since the validator trusts + * those certificates
  • + *
+ * Usage: + *
    + *
  1. Clone and build the EC security validator from + * https://ec.europa.eu/digital-building-blocks/code/projects/EDELIVERY/repos/edelivery2-as4-security-validator/browse
  2. + *
  3. Start the validator with ./start.sh
  4. + *
  5. Run this class as a Java application
  6. + *
+ * + * @author Philip Helger + */ +public final class MainPhase4EDelivery2SendToValidator +{ + private static final Logger LOGGER = Phase4LoggerFactory.getLogger (MainPhase4EDelivery2SendToValidator.class); + + /** The validator's receive endpoint - signature query param tells it what algorithm to expect */ + private static final String VALIDATOR_RECEIVE_URL = "http://localhost:8080/as4/receive?signature=eddsa"; + + /** + * Path to the validator's keystore - we reuse it since the validator trusts its own "blue" certs. + * Can be overridden via system property -Dvalidator.keystore.path=... + */ + private static final String VALIDATOR_KEYSTORE_PATH = System.getProperty ("validator.keystore.path", + System.getProperty ("user.home") + + "/dev/git-thirdparty/edelivery2-as4-security-validator/edelivery2-as4-security-validator-boot/src/main/config/keystore/gateway_keystore.p12"); + private static final String VALIDATOR_KEYSTORE_PASSWORD = "security"; + + /** Alias for the EdDSA signing key in the validator's keystore */ + private static final String SIGNING_KEY_ALIAS = "blue_eddsa_sign"; + + /** Alias for the X25519 encryption key in the validator's keystore */ + private static final String ENCRYPTION_KEY_ALIAS = "blue_eddsa_decrypt"; + + /** Party ID type as required by eDelivery AS4 2.0 */ + private static final String PARTY_ID_TYPE = "urn:oasis:names:tc:ebcore:partyid-type:unregistered"; + + /** Party ID value (matching the validator's SOAP template) */ + private static final String PARTY_ID = "domibus-blue"; + + private MainPhase4EDelivery2SendToValidator () + {} + + private static IAS4CryptoFactory _loadCryptoFactory () throws Exception + { + final File aKSFile = new File (VALIDATOR_KEYSTORE_PATH).getAbsoluteFile (); + if (!aKSFile.exists ()) + throw new IllegalStateException ("Validator keystore not found at '" + + aKSFile.getAbsolutePath () + + "'. Please adjust VALIDATOR_KEYSTORE_PATH."); + + LOGGER.info ("Loading validator keystore from: " + aKSFile.getAbsolutePath ()); + + final KeyStore aKeyStore = KeyStoreHelper.loadKeyStoreDirect (EKeyStoreType.PKCS12, + aKSFile.getAbsolutePath (), + VALIDATOR_KEYSTORE_PASSWORD.toCharArray ()); + if (aKeyStore == null) + throw new IllegalStateException ("Failed to load validator keystore"); + + // Use the same keystore as truststore since the validator trusts "blue" certs + return new AS4CryptoFactoryInMemoryKeyStore (aKeyStore, + SIGNING_KEY_ALIAS, + VALIDATOR_KEYSTORE_PASSWORD.toCharArray (), + aKeyStore); + } + + public static void main (final String [] args) + { + // Register BouncyCastle for Ed25519/X25519 support + Security.addProvider (new BouncyCastleProvider ()); + + WebScopeManager.onGlobalBegin (MockServletContext.create ()); + + // Enable message dumping for debugging + AS4DumpManager.setIncomingDumper (new AS4IncomingDumperFileBased ()); + AS4DumpManager.setOutgoingDumper (new AS4OutgoingDumperFileBased ()); + + try (final WebScoped w = new WebScoped ()) + { + final IAS4CryptoFactory aCryptoFactory = _loadCryptoFactory (); + + // Extract the receiver encryption certificate (X25519) from keystore + final X509Certificate aReceiverEncCert = (X509Certificate) aCryptoFactory.getKeyStore () + .getCertificate (ENCRYPTION_KEY_ALIAS); + if (aReceiverEncCert == null) + throw new IllegalStateException ("Failed to find encryption certificate with alias '" + + ENCRYPTION_KEY_ALIAS + + "' in keystore"); + + LOGGER.info ("Using receiver encryption certificate: " + aReceiverEncCert.getSubjectX500Principal ().getName ()); + + // Simple XML payload + final byte [] aPayloadBytes = ("\n" + + "\n" + + " Hello from phase4 eDelivery AS4 2.0!\n" + + "").getBytes (StandardCharsets.UTF_8); + + // Send using the EdDSA/X25519 builder + LOGGER.info ("--- Sending EdDSA/X25519 message to validator at " + VALIDATOR_RECEIVE_URL + " ---"); + + final EAS4UserMessageSendResult eResult; + eResult = Phase4EDelivery2Sender.builderEdDSA () + .cryptoFactory (aCryptoFactory) + .senderParticipantID (Phase4EDelivery2Sender.IF.createParticipantIdentifier (PARTY_ID_TYPE, + PARTY_ID)) + .receiverParticipantID (Phase4EDelivery2Sender.IF.createParticipantIdentifier (PARTY_ID_TYPE, + PARTY_ID)) + .fromPartyIDType (PARTY_ID_TYPE) + .fromPartyID (PARTY_ID) + .fromRole (CAS4.DEFAULT_INITIATOR_URL) + .toPartyIDType (PARTY_ID_TYPE) + .toPartyID (PARTY_ID) + .toRole (CAS4.DEFAULT_RESPONDER_URL) + .service (CAS4.DEFAULT_SERVICE_URL) + .action (CAS4.DEFAULT_ACTION_URL) + .payload (AS4OutgoingAttachment.builder ().data (aPayloadBytes).mimeTypeXML ()) + .receiverEndpointDetails (aReceiverEncCert, VALIDATOR_RECEIVE_URL) + .buildMessageCallback (new IAS4ClientBuildMessageCallback () + { + public void onAS4Message (@NonNull final AbstractAS4Message aMsg) + { + final AS4UserMessage aUserMsg = (AS4UserMessage) aMsg; + LOGGER.info ("Sending AS4 message with ID: " + + aUserMsg.getEbms3UserMessage () + .getMessageInfo () + .getMessageId ()); + } + }) + .rawResponseConsumer (aResponseMsg -> LOGGER.info ("Response received:\n " + + new String (aResponseMsg.getResponseContent (), + StandardCharsets.UTF_8))) + .sendMessageAndCheckForReceipt (); + + LOGGER.info ("Send result: " + eResult); + + if (eResult.isSuccess ()) + LOGGER.info ("SUCCESS - eDelivery AS4 2.0 EdDSA message was accepted by the validator!"); + else + LOGGER.error ("FAILED - Result: " + eResult); + } + catch (final Exception ex) + { + LOGGER.error ("Error sending eDelivery AS4 2.0 message", ex); + } + finally + { + WebScopeManager.onGlobalEnd (); + } + } +} diff --git a/phase4-edelivery2-client/src/test/java/com/helger/phase4/edelivery2/SPITest.java b/phase4-edelivery2-client/src/test/java/com/helger/phase4/edelivery2/SPITest.java new file mode 100644 index 000000000..144b00c6a --- /dev/null +++ b/phase4-edelivery2-client/src/test/java/com/helger/phase4/edelivery2/SPITest.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.edelivery2; + +import org.junit.Test; + +import com.helger.unittestext.SPITestHelper; + +/** + * Test SPI definitions + * + * @author Philip Helger + */ +public final class SPITest +{ + @Test + public void testBasic () throws Exception + { + SPITestHelper.testIfAllSPIImplementationsAreValid (); + } +} diff --git a/phase4-lib/src/main/java/com/helger/phase4/incoming/AS4IncomingHandler.java b/phase4-lib/src/main/java/com/helger/phase4/incoming/AS4IncomingHandler.java index f80458650..04a122cc1 100644 --- a/phase4-lib/src/main/java/com/helger/phase4/incoming/AS4IncomingHandler.java +++ b/phase4-lib/src/main/java/com/helger/phase4/incoming/AS4IncomingHandler.java @@ -837,56 +837,57 @@ public static IAS4IncomingMessageState processEbmsMessage (@NonNull @WillNotClos _decompressAttachments (aDecryptedAttachments, aEbmsUserMessage, aIncomingState); } else - { - // Signal message + if (aEbmsSignalMessage != null) + { + // Signal message - // Pull-request also requires PMode - if (aEbmsPullRequest != null) - if (aPMode == null) - throw new Phase4IncomingException ("No AS4 P-Mode configuration found for PullRequest!"); + // Pull-request also requires PMode + if (aEbmsPullRequest != null) + if (aPMode == null) + throw new Phase4IncomingException ("No AS4 P-Mode configuration found for PullRequest!"); - if (aValidator != null) - { - if (aAS4ProfileSelector.validateAgainstProfile ()) + if (aValidator != null) { - final ErrorList aErrorList = new ErrorList (); - if (aPMode != null) - aValidator.validatePMode (aPMode, aErrorList, EAS4ProfileValidationMode.SIGNAL_MESSAGE); - aValidator.validateSignalMessage (aEbmsSignalMessage, aErrorList); - - if (aErrorList.containsAtLeastOneError ()) + if (aAS4ProfileSelector.validateAgainstProfile ()) { - LOGGER.error ("Error validating incoming AS4 SignalMessage with the profile '" + - aProfile.getDisplayName () + - "'"); + final ErrorList aErrorList = new ErrorList (); + if (aPMode != null) + aValidator.validatePMode (aPMode, aErrorList, EAS4ProfileValidationMode.SIGNAL_MESSAGE); + aValidator.validateSignalMessage (aEbmsSignalMessage, aErrorList); - for (final IError aError : aErrorList) + if (aErrorList.containsAtLeastOneError ()) { - final String sDetails = aError.getAsString (aLocale); - if (aError.isError ()) + LOGGER.error ("Error validating incoming AS4 SignalMessage with the profile '" + + aProfile.getDisplayName () + + "'"); + + for (final IError aError : aErrorList) { - LOGGER.error (sDetails); - aEbmsErrorMessagesTarget.add (EEbmsError.EBMS_PROCESSING_MODE_MISMATCH.errorBuilder (aLocale) - .refToMessageInError (aIncomingState.getMessageID ()) - .errorDetail (sDetails) - .build ()); + final String sDetails = aError.getAsString (aLocale); + if (aError.isError ()) + { + LOGGER.error (sDetails); + aEbmsErrorMessagesTarget.add (EEbmsError.EBMS_PROCESSING_MODE_MISMATCH.errorBuilder (aLocale) + .refToMessageInError (aIncomingState.getMessageID ()) + .errorDetail (sDetails) + .build ()); + } + else + LOGGER.warn (sDetails); } - else - LOGGER.warn (sDetails); - } - // Was previously a thrown exception - that's why we break here - return aIncomingState; + // Was previously a thrown exception - that's why we break here + return aIncomingState; + } + } + else + { + LOGGER.warn ("The AS4 profile '" + + sProfileID + + "' has a validation configured, but the usage was disabled using the AS4ProfileSelector"); } - } - else - { - LOGGER.warn ("The AS4 profile '" + - sProfileID + - "' has a validation configured, but the usage was disabled using the AS4ProfileSelector"); } } - } final boolean bUseDecryptedSoap = aIncomingState.hasDecryptedSoapDocument (); final Document aRealSoapDoc = bUseDecryptedSoap ? aIncomingState.getDecryptedSoapDocument () : aSoapDocument; diff --git a/phase4-profile-edelivery2/pom.xml b/phase4-profile-edelivery2/pom.xml new file mode 100644 index 000000000..26b379e1d --- /dev/null +++ b/phase4-profile-edelivery2/pom.xml @@ -0,0 +1,94 @@ + + + + 4.0.0 + + com.helger.phase4 + phase4-parent-pom + 4.3.3-SNAPSHOT + + phase4-profile-edelivery2 + bundle + phase4-profile-edelivery2 + eDelivery AS4 2.0 profile + https://github.com/phax/phase4/phase4-profile-edelivery2 + 2026 + + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0 + repo + + + + + + com.helger.phase4 + phase4-lib + + + + jakarta.servlet + jakarta.servlet-api + provided + + + + junit + junit + test + + + org.slf4j + slf4j-simple + test + + + com.helger.commons + ph-unittest-support-ext + test + + + com.helger.photon + ph-oton-app + test + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + com.helger.phase4.profile.edelivery2 + com.helger.phase4.profile.edelivery2.* + !org.jspecify.annotations.*,* + osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)" + osgi.serviceloader; osgi.serviceloader=com.helger.phase4.profile.IAS4ProfileRegistrarSPI + + + + + + diff --git a/phase4-profile-edelivery2/src/etc/javadoc.css b/phase4-profile-edelivery2/src/etc/javadoc.css new file mode 100644 index 000000000..d739e4ac7 --- /dev/null +++ b/phase4-profile-edelivery2/src/etc/javadoc.css @@ -0,0 +1,583 @@ +/** + * Copyright (C) 2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * based on phloc javadoc CSS. + * (c) 2011-2014 phloc systems. + * Derived from the original javadoc CSS from Sun JDK + */ + +body { + background-color: #FFFFFF; + color: #353833; + font-family: Arial, Helvetica, sans-serif; + font-size: 76%; + margin: 0; +} + +a:link,a:visited { + color: #880000; + text-decoration: none; +} + +a:hover,a:focus { + color: #BB2222; + text-decoration: none; +} + +a:active { + color: #4C6B87; + text-decoration: none; +} + +a[name] { + color: #353833; +} + +a[name]:hover { + color: #353833; + text-decoration: none; +} + +pre { + font-size: 1.3em; +} + +h1 { + font-size: 1.8em; +} + +h2 { + font-size: 1.5em; +} + +h3 { + font-size: 1.4em; +} + +h4 { + font-size: 1.3em; +} + +h5 { + font-size: 1.2em; +} + +h6 { + font-size: 1.1em; +} + +ul { + list-style-type: disc; +} + +code,tt { + font-size: 1.2em; +} + +dt code { + font-size: 1.2em; +} + +table tr td dt code { + font-size: 1.2em; + vertical-align: top; +} + +sup { + font-size: 0.6em; +} + +.clear { + clear: both; + height: 0; + overflow: hidden; +} + +.aboutLanguage { + float: right; + font-size: 0.8em; + margin-top: -7px; + padding: 0 21px; + z-index: 200; +} + +.legalCopy { + margin-left: 0.5em; +} + +.bar a,.bar a:link,.bar a:visited,.bar a:active { + color: #FFFFFF; + text-decoration: none; +} + +.bar a:hover,.bar a:focus { + color: #BB7A2A; +} + +.tab { + background-color: #0066FF; + background-image: url("resources/titlebar.gif"); + background-position: left top; + background-repeat: no-repeat; + color: #FFFFFF; + font-weight: bold; + padding: 8px; + width: 5em; +} + +.bar { + background-image: url("resources/background.gif"); + background-repeat: repeat-x; + color: #FFFFFF; + font-size: 1em; + height: auto; + margin: 0; + padding: 0.8em 0.5em 0.4em 0.8em; +} + +.topNav { + background-image: url("resources/background.gif"); + background-repeat: repeat-x; + clear: right; + color: #FFFFFF; + float: left; + height: 2.8em; + overflow: hidden; + padding: 10px 0 0; + width: 100%; +} + +.bottomNav { + background-image: url("resources/background.gif"); + background-repeat: repeat-x; + clear: right; + color: #FFFFFF; + float: left; + height: 2.8em; + margin-top: 10px; + overflow: hidden; + padding: 10px 0 0; + width: 100%; +} + +.subNav { + background-color: #DEE3E9; + border-bottom: 1px solid #9EADC0; + float: left; + overflow: hidden; + width: 100%; +} + +.subNav div { + clear: left; + float: left; + padding: 0 0 5px 6px; +} + +ul.navList,ul.subNavList { + float: left; + margin: 0 25px 0 0; + padding: 0; +} + +ul.navList li { + float: left; + list-style: none outside none; + padding: 3px 6px; +} + +ul.subNavList li { + float: left; + font-size: 90%; + list-style: none outside none; +} + +.topNav a:link,.topNav a:active,.topNav a:visited,.bottomNav a:link,.bottomNav a:active,.bottomNav a:visited + { + color: #FFFFFF; + text-decoration: none; +} + +.topNav a:hover,.bottomNav a:hover { + color: #BB7A2A; + text-decoration: none; +} + +.navBarCell1Rev { + background-color: #A88834; + background-image: url("resources/tab.gif"); + border: 1px solid #C9AA44; + color: #FFFFFF; + margin: auto 5px; +} + +.header,.footer { + clear: both; + margin: 0 20px; + padding: 5px 0 0; +} + +.indexHeader { + margin: 10px; + position: relative; +} + +.indexHeader h1 { + font-size: 1.3em; +} + +.title { + color: #880000; + margin: 10px 0; +} + +.subTitle { + margin: 5px 0 0; +} + +.header ul { + margin: 0 0 25px; + padding: 0; +} + +.footer ul { + margin: 20px 0 5px; +} + +.header ul li,.footer ul li { + font-size: 1.2em; + list-style: none outside none; +} + +div.details ul.blockList ul.blockList ul.blockList li.blockList h4,div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 + { + background-color: #DEE3E9; + border-bottom: 1px solid #9EADC0; + border-top: 1px solid #9EADC0; + margin: 0 0 6px -8px; + padding: 2px 5px; +} + +ul.blockList ul.blockList ul.blockList li.blockList h3 { + background-color: #DEE3E9; + border-bottom: 1px solid #9EADC0; + border-top: 1px solid #9EADC0; + margin: 0 0 6px -8px; + padding: 2px 5px; +} + +ul.blockList ul.blockList li.blockList h3 { + margin: 15px 0; + padding: 0; +} + +ul.blockList li.blockList h2 { + padding: 0 0 20px; +} + +.contentContainer,.sourceContainer,.classUseContainer,.serializedFormContainer,.constantValuesContainer + { + clear: both; + padding: 10px 20px; + position: relative; +} + +.indexContainer { + font-size: 1em; + margin: 10px; + position: relative; +} + +.indexContainer h2 { + font-size: 1.1em; + padding: 0 0 3px; +} + +.indexContainer ul { + margin: 0; + padding: 0; +} + +.indexContainer ul li { + list-style: none outside none; +} + +.contentContainer .description dl dt,.contentContainer .details dl dt,.serializedFormContainer dl dt + { + color: #4E4E4E; + font-size: 1.1em; + font-weight: bold; + margin: 10px 0 0; +} + +.contentContainer .description dl dd,.contentContainer .details dl dd,.serializedFormContainer dl dd + { + margin: 10px 0 10px 20px; +} + +.serializedFormContainer dl.nameValue dt { + display: inline; + font-size: 1.1em; + font-weight: bold; + margin-left: 1px; +} + +.serializedFormContainer dl.nameValue dd { + display: inline; + font-size: 1.1em; +} + +ul.horizontal li { + display: inline; + font-size: 0.9em; +} + +ul.inheritance { + margin: 0; + padding: 0; +} + +ul.inheritance li { + display: inline; + list-style: none outside none; +} + +ul.inheritance li ul.inheritance { + margin-left: 15px; + padding-left: 15px; + padding-top: 1px; +} + +ul.blockList,ul.blockListLast { + margin: 10px 0; + padding: 0; +} + +ul.blockList li.blockList,ul.blockListLast li.blockList { + list-style: none outside none; + margin-bottom: 25px; +} + +ul.blockList ul.blockList li.blockList,ul.blockList ul.blockListLast li.blockList + { + background-color: #F9F9F9; + border: 1px solid #9EADC0; + padding: 0 20px 5px 10px; +} + +ul.blockList ul.blockList ul.blockList li.blockList,ul.blockList ul.blockList ul.blockListLast li.blockList + { + -moz-border-bottom-colors: none; + -moz-border-left-colors: none; + -moz-border-right-colors: none; + -moz-border-top-colors: none; + background-color: #FFFFFF; + border-color: currentColor #9EADC0 #9EADC0; + border-image: none; + border-right: 1px solid #9EADC0; + border-style: none solid solid; + border-width: medium 1px 1px; + padding: 0 0 5px 8px; +} + +ul.blockList ul.blockList ul.blockList ul.blockList li.blockList { + -moz-border-bottom-colors: none; + -moz-border-left-colors: none; + -moz-border-right-colors: none; + -moz-border-top-colors: none; + border-color: currentColor currentColor #9EADC0; + border-image: none; + border-style: none none solid; + border-width: medium medium 1px; + margin-left: 0; + padding-bottom: 15px; + padding-left: 0; +} + +ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast { + border-bottom: medium none; + list-style: none outside none; + padding-bottom: 0; +} + +table tr td dl,table tr td dl dt,table tr td dl dd { + margin-bottom: 1px; + margin-top: 0; +} + +.contentContainer table,.classUseContainer table,.constantValuesContainer table + { + border-bottom: 1px solid #9EADC0; + width: 100%; +} + +.contentContainer ul li table,.classUseContainer ul li table,.constantValuesContainer ul li table + { + width: 100%; +} + +.contentContainer .description table,.contentContainer .details table { + border-bottom: medium none; +} + +.contentContainer ul li table th.colOne,.contentContainer ul li table th.colFirst,.contentContainer ul li table th.colLast,.classUseContainer ul li table th,.constantValuesContainer ul li table th,.contentContainer ul li table td.colOne,.contentContainer ul li table td.colFirst,.contentContainer ul li table td.colLast,.classUseContainer ul li table td,.constantValuesContainer ul li table td + { + padding-right: 20px; + vertical-align: top; +} + +.contentContainer ul li table th.colLast,.classUseContainer ul li table th.colLast,.constantValuesContainer ul li table th.colLast,.contentContainer ul li table td.colLast,.classUseContainer ul li table td.colLast,.constantValuesContainer ul li table td.colLast,.contentContainer ul li table th.colOne,.classUseContainer ul li table th.colOne,.contentContainer ul li table td.colOne,.classUseContainer ul li table td.colOne + { + padding-right: 3px; +} + +.overviewSummary caption,.packageSummary caption,.contentContainer ul.blockList li.blockList caption,.summary caption,.classUseContainer caption,.constantValuesContainer caption + { + background-repeat: no-repeat; + clear: none; + color: #FFFFFF; + font-weight: bold; + margin: 0; + overflow: hidden; + padding: 0; + position: relative; + text-align: left; +} + +caption a:link,caption a:hover,caption a:active,caption a:visited { + color: #FFFFFF; +} + +.overviewSummary caption span,.packageSummary caption span,.contentContainer ul.blockList li.blockList caption span,.summary caption span,.classUseContainer caption span,.constantValuesContainer caption span + { + background-image: url("resources/titlebar.gif"); + display: block; + float: left; + height: 18px; + padding-left: 8px; + padding-top: 8px; + white-space: nowrap; +} + +.overviewSummary .tabEnd,.packageSummary .tabEnd,.contentContainer ul.blockList li.blockList .tabEnd,.summary .tabEnd,.classUseContainer .tabEnd,.constantValuesContainer .tabEnd + { + background-image: url("resources/titlebar_end.gif"); + background-position: right top; + background-repeat: no-repeat; + float: left; + position: relative; + width: 10px; +} + +ul.blockList ul.blockList li.blockList table { + margin: 0 0 12px; + width: 100%; +} + +.tableSubHeadingColor { + background-color: #EEEEFF; +} + +.altColor { + background-color: #EEEEEF; +} + +.rowColor { + background-color: #FFFFFF; +} + +.overviewSummary td,.packageSummary td,.contentContainer ul.blockList li.blockList td,.summary td,.classUseContainer td,.constantValuesContainer td + { + padding: 3px 3px 3px 7px; + text-align: left; +} + +th.colFirst,th.colLast,th.colOne,.constantValuesContainer th { + background: none repeat scroll 0 0 #DEE3E9; + border-bottom: 1px solid #9EADC0; + border-top: 1px solid #9EADC0; + padding: 3px 3px 3px 7px; + text-align: left; +} + +td.colOne a:link,td.colOne a:active,td.colOne a:visited,td.colOne a:hover,td.colFirst a:link,td.colFirst a:active,td.colFirst a:visited,td.colFirst a:hover,td.colLast a:link,td.colLast a:active,td.colLast a:visited,td.colLast a:hover,.constantValuesContainer td a:link,.constantValuesContainer td a:active,.constantValuesContainer td a:visited,.constantValuesContainer td a:hover + { + font-weight: bold; +} + +td.colFirst,th.colFirst { + border-left: 1px solid #9EADC0; + white-space: nowrap; +} + +td.colLast,th.colLast { + border-right: 1px solid #9EADC0; +} + +td.colOne,th.colOne { + border-left: 1px solid #9EADC0; + border-right: 1px solid #9EADC0; +} + +table.overviewSummary { + margin-left: 0; + padding: 0; +} + +table.overviewSummary td.colFirst,table.overviewSummary th.colFirst,table.overviewSummary td.colOne,table.overviewSummary th.colOne + { + vertical-align: middle; + width: 25%; +} + +table.packageSummary td.colFirst,table.overviewSummary th.colFirst { + vertical-align: middle; + width: 25%; +} + +.description pre { + margin-top: 0; +} + +.deprecatedContent { + margin: 0; + padding: 10px 0; +} + +.docSummary { + padding: 0; +} + +.sourceLineNo { + color: #008000; + padding: 0 30px 0 0; +} + +h1.hidden { + font-size: 0.9em; + overflow: hidden; + visibility: hidden; +} + +.block { + display: block; + margin: 3px 0 0; +} + +.strong { + font-weight: bold; +} diff --git a/phase4-profile-edelivery2/src/etc/license-template.txt b/phase4-profile-edelivery2/src/etc/license-template.txt new file mode 100644 index 000000000..334a66dac --- /dev/null +++ b/phase4-profile-edelivery2/src/etc/license-template.txt @@ -0,0 +1,14 @@ +Copyright (C) 2026 Philip Helger (www.helger.com) +philip[at]helger[dot]com + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/phase4-profile-edelivery2/src/main/java/com/helger/phase4/profile/edelivery2/AS4EDelivery2ProfileRegistarSPI.java b/phase4-profile-edelivery2/src/main/java/com/helger/phase4/profile/edelivery2/AS4EDelivery2ProfileRegistarSPI.java new file mode 100644 index 000000000..94f1bdcfe --- /dev/null +++ b/phase4-profile-edelivery2/src/main/java/com/helger/phase4/profile/edelivery2/AS4EDelivery2ProfileRegistarSPI.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.profile.edelivery2; + +import org.jspecify.annotations.NonNull; +import org.slf4j.Logger; + +import com.helger.annotation.style.IsSPIImplementation; +import com.helger.phase4.logging.Phase4LoggerFactory; +import com.helger.phase4.model.pmode.IPModeIDProvider; +import com.helger.phase4.profile.AS4Profile; +import com.helger.phase4.profile.IAS4ProfilePModeProvider; +import com.helger.phase4.profile.IAS4ProfileRegistrar; +import com.helger.phase4.profile.IAS4ProfileRegistrarSPI; + +/** + * Library specific implementation of {@link IAS4ProfileRegistrarSPI} for eDelivery AS4 2.0. + * + * @author Philip Helger + * @since 4.4.0 + */ +@IsSPIImplementation +public final class AS4EDelivery2ProfileRegistarSPI implements IAS4ProfileRegistrarSPI +{ + /** Profile ID for eDelivery 2.0 Common Usage Profile (EdDSA/X25519) - four corner model */ + public static final String AS4_PROFILE_ID_EDDSA_FOUR_CORNER = "edelivery2-eddsa"; + public static final String AS4_PROFILE_NAME_EDDSA_FOUR_CORNER = "eDelivery AS4 2.0 EdDSA (four corner)"; + + /** Profile ID for eDelivery 2.0 Common Usage Profile (EdDSA/X25519) - two corner model */ + public static final String AS4_PROFILE_ID_EDDSA_TWO_CORNER = "edelivery2-eddsa-two-corner"; + public static final String AS4_PROFILE_NAME_EDDSA_TWO_CORNER = "eDelivery AS4 2.0 EdDSA (two corner)"; + + /** Profile ID for eDelivery 2.0 Alternative EC Profile (ECDSA/ECDH-ES) - four corner model */ + public static final String AS4_PROFILE_ID_ECDSA_FOUR_CORNER = "edelivery2-ecdsa"; + public static final String AS4_PROFILE_NAME_ECDSA_FOUR_CORNER = "eDelivery AS4 2.0 ECDSA (four corner)"; + + /** Profile ID for eDelivery 2.0 Alternative EC Profile (ECDSA/ECDH-ES) - two corner model */ + public static final String AS4_PROFILE_ID_ECDSA_TWO_CORNER = "edelivery2-ecdsa-two-corner"; + public static final String AS4_PROFILE_NAME_ECDSA_TWO_CORNER = "eDelivery AS4 2.0 ECDSA (two corner)"; + + public static final IPModeIDProvider PMODE_ID_PROVIDER = IPModeIDProvider.DEFAULT_DYNAMIC; + + private static final Logger LOGGER = Phase4LoggerFactory.getLogger (AS4EDelivery2ProfileRegistarSPI.class); + + public void registerAS4Profile (@NonNull final IAS4ProfileRegistrar aRegistrar) + { + // EdDSA/X25519 PMode provider (Common Usage Profile) + final IAS4ProfilePModeProvider aEdDSAPModeProvider = (i, r, a) -> EDelivery2PMode.createEDelivery2PMode (i, + r, + a, + PMODE_ID_PROVIDER, + true, + EDelivery2PMode.generatePModeLegSecurityEdDSA ()); + + // ECDSA/ECDH-ES PMode provider (Alternative EC Profile) + final IAS4ProfilePModeProvider aECDSAPModeProvider = (i, r, a) -> EDelivery2PMode.createEDelivery2PMode (i, + r, + a, + PMODE_ID_PROVIDER, + true, + EDelivery2PMode.generatePModeLegSecurityECDSA ()); + + // Register EdDSA four corner profile + if (LOGGER.isDebugEnabled ()) + LOGGER.debug ("Registering phase4 profile '" + AS4_PROFILE_ID_EDDSA_FOUR_CORNER + "'"); + aRegistrar.registerProfile (new AS4Profile (AS4_PROFILE_ID_EDDSA_FOUR_CORNER, + AS4_PROFILE_NAME_EDDSA_FOUR_CORNER, + () -> new EDelivery2CompatibilityValidator ().setExpectFourCornerModel (true) + .setAllowECDSA (false), + aEdDSAPModeProvider, + PMODE_ID_PROVIDER, + false, + false)); + + // Register EdDSA two corner profile + if (LOGGER.isDebugEnabled ()) + LOGGER.debug ("Registering phase4 profile '" + AS4_PROFILE_ID_EDDSA_TWO_CORNER + "'"); + aRegistrar.registerProfile (new AS4Profile (AS4_PROFILE_ID_EDDSA_TWO_CORNER, + AS4_PROFILE_NAME_EDDSA_TWO_CORNER, + () -> new EDelivery2CompatibilityValidator ().setExpectFourCornerModel (false) + .setAllowECDSA (false), + aEdDSAPModeProvider, + PMODE_ID_PROVIDER, + false, + false)); + + // Register ECDSA four corner profile + if (LOGGER.isDebugEnabled ()) + LOGGER.debug ("Registering phase4 profile '" + AS4_PROFILE_ID_ECDSA_FOUR_CORNER + "'"); + aRegistrar.registerProfile (new AS4Profile (AS4_PROFILE_ID_ECDSA_FOUR_CORNER, + AS4_PROFILE_NAME_ECDSA_FOUR_CORNER, + () -> new EDelivery2CompatibilityValidator ().setExpectFourCornerModel (true) + .setAllowECDSA (true), + aECDSAPModeProvider, + PMODE_ID_PROVIDER, + false, + false)); + + // Register ECDSA two corner profile + if (LOGGER.isDebugEnabled ()) + LOGGER.debug ("Registering phase4 profile '" + AS4_PROFILE_ID_ECDSA_TWO_CORNER + "'"); + aRegistrar.registerProfile (new AS4Profile (AS4_PROFILE_ID_ECDSA_TWO_CORNER, + AS4_PROFILE_NAME_ECDSA_TWO_CORNER, + () -> new EDelivery2CompatibilityValidator ().setExpectFourCornerModel (false) + .setAllowECDSA (true), + aECDSAPModeProvider, + PMODE_ID_PROVIDER, + false, + false)); + } +} diff --git a/phase4-profile-edelivery2/src/main/java/com/helger/phase4/profile/edelivery2/EDelivery2CompatibilityValidator.java b/phase4-profile-edelivery2/src/main/java/com/helger/phase4/profile/edelivery2/EDelivery2CompatibilityValidator.java new file mode 100644 index 000000000..c0ea10f32 --- /dev/null +++ b/phase4-profile-edelivery2/src/main/java/com/helger/phase4/profile/edelivery2/EDelivery2CompatibilityValidator.java @@ -0,0 +1,479 @@ +/* + * Copyright (C) 2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.profile.edelivery2; + +import java.util.List; + +import org.jspecify.annotations.NonNull; + +import com.helger.annotation.Nonempty; +import com.helger.base.enforce.ValueEnforcer; +import com.helger.base.string.StringHelper; +import com.helger.diagnostics.error.IError; +import com.helger.diagnostics.error.SingleError; +import com.helger.diagnostics.error.list.ErrorList; +import com.helger.phase4.CAS4; +import com.helger.phase4.attachment.EAS4CompressionMode; +import com.helger.phase4.crypto.ECryptoAlgorithmCrypt; +import com.helger.phase4.crypto.ECryptoAlgorithmSign; +import com.helger.phase4.crypto.ECryptoAlgorithmSignDigest; +import com.helger.phase4.ebms3header.Ebms3From; +import com.helger.phase4.ebms3header.Ebms3MessageProperties; +import com.helger.phase4.ebms3header.Ebms3Property; +import com.helger.phase4.ebms3header.Ebms3SignalMessage; +import com.helger.phase4.ebms3header.Ebms3To; +import com.helger.phase4.ebms3header.Ebms3UserMessage; +import com.helger.phase4.mgr.MetaAS4Manager; +import com.helger.phase4.model.EMEP; +import com.helger.phase4.model.EMEPBinding; +import com.helger.phase4.model.ESoapVersion; +import com.helger.phase4.model.pmode.IPMode; +import com.helger.phase4.model.pmode.PModePayloadService; +import com.helger.phase4.model.pmode.PModeValidationException; +import com.helger.phase4.model.pmode.leg.EPModeSendReceiptReplyPattern; +import com.helger.phase4.model.pmode.leg.PModeLeg; +import com.helger.phase4.model.pmode.leg.PModeLegErrorHandling; +import com.helger.phase4.model.pmode.leg.PModeLegProtocol; +import com.helger.phase4.model.pmode.leg.PModeLegSecurity; +import com.helger.phase4.profile.IAS4ProfileValidator; +import com.helger.phase4.wss.EWSSVersion; + +/** + * Validate certain requirements imposed by the eDelivery AS4 2.0 profile. Supports both the Common + * Usage Profile (EdDSA/X25519) and the Alternative Elliptic Curve Profile (ECDSA/ECDH-ES with + * secp256r1). + * + * @author Philip Helger + * @since 4.4.0 + */ +public class EDelivery2CompatibilityValidator implements IAS4ProfileValidator +{ + public static final boolean DEFAULT_EXPECT_FOUR_CORNER_MODEL = true; + + private boolean m_bExpectFourCornerModel = DEFAULT_EXPECT_FOUR_CORNER_MODEL; + private boolean m_bAllowECDSA; + + public EDelivery2CompatibilityValidator () + {} + + public final boolean isExpectFourCornerModel () + { + return m_bExpectFourCornerModel; + } + + @NonNull + public final EDelivery2CompatibilityValidator setExpectFourCornerModel (final boolean b) + { + m_bExpectFourCornerModel = b; + return this; + } + + /** + * @return true if ECDSA signing (alternative EC profile) is also accepted, + * false if only EdDSA/Ed25519 (common usage profile) is accepted. + */ + public final boolean isAllowECDSA () + { + return m_bAllowECDSA; + } + + /** + * Set whether ECDSA signing is also accepted as an alternative to EdDSA/Ed25519. + * + * @param b + * true to allow ECDSA, false to only allow EdDSA. + * @return this for chaining + */ + @NonNull + public final EDelivery2CompatibilityValidator setAllowECDSA (final boolean b) + { + m_bAllowECDSA = b; + return this; + } + + @NonNull + private static IError _createError (@NonNull final String sMsg) + { + return SingleError.builderError ().errorText (sMsg).build (); + } + + @NonNull + private static IError _createWarn (@NonNull final String sMsg) + { + return SingleError.builderWarn ().errorText (sMsg).build (); + } + + private void _checkIfLegIsValid (@NonNull final ErrorList aErrorList, + @NonNull final PModeLeg aPModeLeg, + @NonNull @Nonempty final String sFieldPrefix) + { + final PModeLegProtocol aLegProtocol = aPModeLeg.getProtocol (); + if (aLegProtocol == null) + { + aErrorList.add (_createError (sFieldPrefix + "Protocol is missing")); + } + else + { + // PROTOCOL Address only https allowed (mandatory TLS) + final String sAddressProtocol = aLegProtocol.getAddressProtocol (); + if (StringHelper.isNotEmpty (sAddressProtocol)) + { + if (sAddressProtocol.equalsIgnoreCase ("https")) + { + // Always okay - TLS is mandatory for AS4 2.0 + } + else + if (sAddressProtocol.equalsIgnoreCase ("http")) + { + aErrorList.add (_createWarn (sFieldPrefix + + "AddressProtocol 'http' is used but eDelivery AS4 2.0 requires TLS (https)")); + } + else + { + aErrorList.add (_createError (sFieldPrefix + "AddressProtocol '" + sAddressProtocol + "' is unsupported")); + } + } + + final ESoapVersion eSOAPVersion = aLegProtocol.getSoapVersion (); + if (!eSOAPVersion.isAS4Default ()) + { + aErrorList.add (_createError (sFieldPrefix + + "SoapVersion '" + + eSOAPVersion.getVersion () + + "' is unsupported")); + } + } + + // Only check the security features if a Security Leg is currently present + final PModeLegSecurity aPModeLegSecurity = aPModeLeg.getSecurity (); + if (aPModeLegSecurity != null) + { + // Check Signature Algorithm - must be EdDSA Ed25519 (or optionally ECDSA) + if (aPModeLegSecurity.getX509SignatureAlgorithm () == null) + { + aErrorList.add (_createError (sFieldPrefix + "Security.X509SignatureAlgorithm is missing")); + } + else + { + final ECryptoAlgorithmSign eSignAlgo = aPModeLegSecurity.getX509SignatureAlgorithm (); + if (eSignAlgo.equals (ECryptoAlgorithmSign.EDDSA_ED25519)) + { + // Common Usage Profile - always valid + } + else + if (m_bAllowECDSA && + (eSignAlgo.equals (ECryptoAlgorithmSign.ECDSA_SHA_256) || + eSignAlgo.equals (ECryptoAlgorithmSign.ECDSA_SHA_384) || + eSignAlgo.equals (ECryptoAlgorithmSign.ECDSA_SHA_512))) + { + // Alternative EC Profile - valid when ECDSA is allowed + } + else + { + aErrorList.add (_createError (sFieldPrefix + + "Security.X509SignatureAlgorithm must use the value '" + + ECryptoAlgorithmSign.EDDSA_ED25519.getID () + + "'" + + (m_bAllowECDSA ? " or an ECDSA algorithm" : "") + + " instead of '" + + eSignAlgo.getID () + + "'")); + } + } + + // Check Hash Function - must be SHA-256 + if (aPModeLegSecurity.getX509SignatureHashFunction () == null) + { + aErrorList.add (_createError (sFieldPrefix + "Security.X509SignatureHashFunction is missing")); + } + else + if (!aPModeLegSecurity.getX509SignatureHashFunction ().equals (ECryptoAlgorithmSignDigest.DIGEST_SHA_256)) + { + aErrorList.add (_createError (sFieldPrefix + + "Security.X509SignatureHashFunction must use the value '" + + ECryptoAlgorithmSignDigest.DIGEST_SHA_256.getID () + + "'")); + } + + // Check Encrypt algorithm - must be AES-128-GCM (no CBC) + if (aPModeLegSecurity.getX509EncryptionAlgorithm () == null) + { + aErrorList.add (_createError (sFieldPrefix + "Security.X509EncryptionAlgorithm is missing")); + } + else + if (!aPModeLegSecurity.getX509EncryptionAlgorithm ().equals (ECryptoAlgorithmCrypt.AES_128_GCM)) + { + aErrorList.add (_createError (sFieldPrefix + + "Security.X509EncryptionAlgorithm must use the value '" + + ECryptoAlgorithmCrypt.AES_128_GCM.getID () + + "' instead of '" + + aPModeLegSecurity.getX509EncryptionAlgorithm ().getID () + + "'")); + } + + // Check WSS Version = 1.1.1 + if (aPModeLegSecurity.getWSSVersion () != null) + { + if (!aPModeLegSecurity.getWSSVersion ().equals (EWSSVersion.WSS_111)) + aErrorList.add (_createError (sFieldPrefix + + "Security.WSSVersion must use the value " + + EWSSVersion.WSS_111 + + " instead of " + + aPModeLegSecurity.getWSSVersion ())); + } + + // PModeAuthorize - Username Tokens are NOT supported in AS4 2.0 + if (aPModeLegSecurity.isPModeAuthorizeDefined ()) + { + if (aPModeLegSecurity.isPModeAuthorize ()) + aErrorList.add (_createError (sFieldPrefix + + "Security.PModeAuthorize must be set to 'false' (Username Tokens are not supported in eDelivery AS4 2.0)")); + } + else + { + aErrorList.add (_createError (sFieldPrefix + "Security.PModeAuthorize is missing")); + } + + // Receipts must be synchronous only in AS4 2.0 + if (aPModeLegSecurity.isSendReceiptDefined ()) + { + if (aPModeLegSecurity.isSendReceipt ()) + { + if (aPModeLegSecurity.getSendReceiptReplyPattern () != EPModeSendReceiptReplyPattern.RESPONSE) + aErrorList.add (_createError (sFieldPrefix + + "Security.SendReceiptReplyPattern must use the value " + + EPModeSendReceiptReplyPattern.RESPONSE + + " instead of " + + aPModeLegSecurity.getSendReceiptReplyPattern () + + " (only synchronous receipts are supported in eDelivery AS4 2.0)")); + } + } + } + else + { + // Security is mandatory in eDelivery AS4 2.0 + aErrorList.add (_createError (sFieldPrefix + + "Security is missing (mandatory signing and encryption required in eDelivery AS4 2.0)")); + } + + // Error Handling + final PModeLegErrorHandling aErrorHandling = aPModeLeg.getErrorHandling (); + if (aErrorHandling != null) + { + if (aErrorHandling.isReportAsResponseDefined ()) + { + if (!aErrorHandling.isReportAsResponse ()) + aErrorList.add (_createError (sFieldPrefix + "ErrorHandling.Report.AsResponse must be 'true'")); + } + else + { + aErrorList.add (_createError (sFieldPrefix + "ErrorHandling.Report.AsResponse is missing")); + } + + if (aErrorHandling.isReportProcessErrorNotifyConsumerDefined ()) + { + if (!aErrorHandling.isReportProcessErrorNotifyConsumer ()) + aErrorList.add (_createWarn (sFieldPrefix + + "ErrorHandling.Report.ProcessErrorNotifyConsumer should be 'true'")); + } + else + { + aErrorList.add (_createError (sFieldPrefix + "ErrorHandling.Report.ProcessErrorNotifyConsumer is missing")); + } + + if (aErrorHandling.isReportProcessErrorNotifyProducerDefined ()) + { + if (!aErrorHandling.isReportProcessErrorNotifyProducer ()) + aErrorList.add (_createWarn (sFieldPrefix + + "ErrorHandling.Report.ProcessErrorNotifyProducer should be 'true'")); + } + else + { + aErrorList.add (_createError (sFieldPrefix + "ErrorHandling.Report.ProcessErrorNotifyProducer is missing")); + } + } + else + { + aErrorList.add (_createError (sFieldPrefix + "ErrorHandling is missing")); + } + } + + @Override + public void validatePMode (@NonNull final IPMode aPMode, + @NonNull final ErrorList aErrorList, + @NonNull final EAS4ProfileValidationMode eValidationMode) + { + ValueEnforcer.notNull (aPMode, "PMode"); + ValueEnforcer.notNull (aErrorList, "ErrorList"); + ValueEnforcer.notNull (eValidationMode, "ValidationMode"); + ValueEnforcer.isTrue (aErrorList.isEmpty (), () -> "Errors in global PMode validation: " + aErrorList.toString ()); + + try + { + MetaAS4Manager.getPModeMgr ().validatePMode (aPMode); + } + catch (final PModeValidationException ex) + { + aErrorList.add (_createError (ex.getMessage ())); + } + + final EMEP eMEP = aPMode.getMEP (); + final EMEPBinding eMEPBinding = aPMode.getMEPBinding (); + + if ((eMEP == EMEP.ONE_WAY && eMEPBinding == EMEPBinding.PUSH) || + (eMEP == EMEP.TWO_WAY && eMEPBinding == EMEPBinding.PUSH_PUSH)) + { + // Valid - eDelivery AS4 2.0 requires one-way/push and two-way/push-push + } + else + { + aErrorList.add (_createError ("An invalid combination of PMode MEP (" + + eMEP + + ") and MEP binding (" + + eMEPBinding + + ") was specified, valid are only one-way/push and two-way/push-push.")); + } + + // Leg1 must be present + final PModeLeg aPModeLeg1 = aPMode.getLeg1 (); + if (aPModeLeg1 == null) + { + aErrorList.add (_createError ("PMode.Leg[1] is missing")); + } + else + { + _checkIfLegIsValid (aErrorList, aPModeLeg1, "PMode.Leg[1]."); + } + + if (eMEP.isTwoWay ()) + { + final PModeLeg aPModeLeg2 = aPMode.getLeg2 (); + if (aPModeLeg2 == null) + { + aErrorList.add (_createError ("PMode.Leg[2] is missing as it specified as TWO-WAY")); + } + else + { + _checkIfLegIsValid (aErrorList, aPModeLeg2, "PMode.Leg[2]."); + } + } + + // Compression application/gzip ONLY + final PModePayloadService aPayloadService = aPMode.getPayloadService (); + if (aPayloadService != null) + { + final EAS4CompressionMode eCompressionMode = aPayloadService.getCompressionMode (); + if (eCompressionMode != null) + { + if (!eCompressionMode.equals (EAS4CompressionMode.GZIP)) + aErrorList.add (_createError ("PMode.PayloadService.CompressionMode must be " + + EAS4CompressionMode.GZIP + + " instead of " + + eCompressionMode)); + } + } + } + + @Override + public void validateUserMessage (@NonNull final Ebms3UserMessage aUserMsg, @NonNull final ErrorList aErrorList) + { + ValueEnforcer.notNull (aUserMsg, "UserMsg"); + + if (aUserMsg.getMessageInfo () == null) + { + aErrorList.add (_createError ("MessageInfo is missing")); + } + else + { + if (StringHelper.isEmpty (aUserMsg.getMessageInfo ().getMessageId ())) + aErrorList.add (_createError ("MessageInfo/MessageId is missing")); + + if (m_bExpectFourCornerModel) + { + final Ebms3MessageProperties aMessageProperties = aUserMsg.getMessageProperties (); + if (aMessageProperties == null) + aErrorList.add (_createError ("MessageProperties is missing but 'originalSender' and 'finalRecipient' properties are required")); + else + { + final List aProps = aMessageProperties.getProperty (); + if (aProps.isEmpty ()) + aErrorList.add (_createError ("MessageProperties/Property must not be empty")); + else + { + String sOriginalSenderC1 = null; + String sFinalRecipientC4 = null; + + for (final Ebms3Property sProperty : aProps) + { + if (sProperty.getName ().equals (CAS4.ORIGINAL_SENDER)) + sOriginalSenderC1 = sProperty.getValue (); + else + if (sProperty.getName ().equals (CAS4.FINAL_RECIPIENT)) + sFinalRecipientC4 = sProperty.getValue (); + } + + if (StringHelper.isEmpty (sOriginalSenderC1)) + aErrorList.add (_createError ("MessageProperties/Property '" + + CAS4.ORIGINAL_SENDER + + "' property is empty or not existant but mandatory")); + if (StringHelper.isEmpty (sFinalRecipientC4)) + aErrorList.add (_createError ("MessageProperties/Property '" + + CAS4.FINAL_RECIPIENT + + "' property is empty or not existant but mandatory")); + } + } + } + } + + if (aUserMsg.getPartyInfo () == null) + { + aErrorList.add (_createError ("PartyInfo is missing")); + } + else + { + final Ebms3From aFrom = aUserMsg.getPartyInfo ().getFrom (); + if (aFrom != null) + { + if (aFrom.getPartyIdCount () > 1) + aErrorList.add (_createError ("PartyInfo/From must contain no more than one PartyID")); + } + + final Ebms3To aTo = aUserMsg.getPartyInfo ().getTo (); + if (aTo != null) + { + if (aTo.getPartyIdCount () > 1) + aErrorList.add (_createError ("PartyInfo/To must contain no more than one PartyID")); + } + } + } + + @Override + public void validateSignalMessage (@NonNull final Ebms3SignalMessage aSignalMsg, @NonNull final ErrorList aErrorList) + { + ValueEnforcer.notNull (aSignalMsg, "SignalMsg"); + + if (aSignalMsg.getMessageInfo () == null) + { + aErrorList.add (_createError ("MessageInfo is missing")); + } + else + { + if (StringHelper.isEmpty (aSignalMsg.getMessageInfo ().getMessageId ())) + aErrorList.add (_createError ("MessageInfo/MessageId is missing")); + } + } +} diff --git a/phase4-profile-edelivery2/src/main/java/com/helger/phase4/profile/edelivery2/EDelivery2PMode.java b/phase4-profile-edelivery2/src/main/java/com/helger/phase4/profile/edelivery2/EDelivery2PMode.java new file mode 100644 index 000000000..fe93c0ccf --- /dev/null +++ b/phase4-profile-edelivery2/src/main/java/com/helger/phase4/profile/edelivery2/EDelivery2PMode.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.profile.edelivery2; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import com.helger.annotation.Nonempty; +import com.helger.annotation.concurrent.Immutable; +import com.helger.base.state.ETriState; +import com.helger.phase4.CAS4; +import com.helger.phase4.crypto.ECryptoAlgorithmCrypt; +import com.helger.phase4.crypto.ECryptoAlgorithmSign; +import com.helger.phase4.crypto.ECryptoAlgorithmSignDigest; +import com.helger.phase4.mgr.MetaAS4Manager; +import com.helger.phase4.model.EMEP; +import com.helger.phase4.model.EMEPBinding; +import com.helger.phase4.model.pmode.IPModeIDProvider; +import com.helger.phase4.model.pmode.PMode; +import com.helger.phase4.model.pmode.PModeParty; +import com.helger.phase4.model.pmode.PModePayloadService; +import com.helger.phase4.model.pmode.PModeReceptionAwareness; +import com.helger.phase4.model.pmode.leg.EPModeSendReceiptReplyPattern; +import com.helger.phase4.model.pmode.leg.PModeAddressList; +import com.helger.phase4.model.pmode.leg.PModeLeg; +import com.helger.phase4.model.pmode.leg.PModeLegBusinessInformation; +import com.helger.phase4.model.pmode.leg.PModeLegErrorHandling; +import com.helger.phase4.model.pmode.leg.PModeLegProtocol; +import com.helger.phase4.model.pmode.leg.PModeLegReliability; +import com.helger.phase4.model.pmode.leg.PModeLegSecurity; +import com.helger.phase4.wss.EWSSVersion; + +/** + * eDelivery AS4 2.0 PMode creation code. + * + * @author Philip Helger + * @since 4.4.0 + */ +@Immutable +public final class EDelivery2PMode +{ + public static final String DEFAULT_AGREEMENT_ID = "urn:as4:agreement"; + + private EDelivery2PMode () + {} + + @NonNull + public static PModeLegProtocol generatePModeLegProtocol (@Nullable final String sAddress) + { + return PModeLegProtocol.createForDefaultSoapVersion (sAddress); + } + + @NonNull + public static PModeLegBusinessInformation generatePModeLegBusinessInformation () + { + final String sService = null; + final String sAction = CAS4.DEFAULT_ACTION_URL; + final Long nPayloadProfileMaxKB = null; + final String sMPCID = CAS4.DEFAULT_MPC_ID; + return PModeLegBusinessInformation.create (sService, sAction, nPayloadProfileMaxKB, sMPCID); + } + + @NonNull + public static PModeLegErrorHandling generatePModeLegErrorHandling () + { + final PModeAddressList aReportSenderErrorsTo = null; + final PModeAddressList aReportReceiverErrorsTo = null; + final ETriState eReportAsResponse = ETriState.TRUE; + final ETriState eReportProcessErrorNotifyConsumer = ETriState.TRUE; + final ETriState eReportProcessErrorNotifyProducer = ETriState.TRUE; + final ETriState eReportDeliveryFailuresNotifyProducer = ETriState.TRUE; + return new PModeLegErrorHandling (aReportSenderErrorsTo, + aReportReceiverErrorsTo, + eReportAsResponse, + eReportProcessErrorNotifyConsumer, + eReportProcessErrorNotifyProducer, + eReportDeliveryFailuresNotifyProducer); + } + + /** + * Generate PMode leg security for the Common Usage Profile (EdDSA/X25519). + * + * @return Never null. + */ + @NonNull + public static PModeLegSecurity generatePModeLegSecurityEdDSA () + { + final PModeLegSecurity aPModeLegSecurity = new PModeLegSecurity (); + aPModeLegSecurity.setWSSVersion (EWSSVersion.WSS_111); + aPModeLegSecurity.setX509SignatureAlgorithm (ECryptoAlgorithmSign.EDDSA_ED25519); + aPModeLegSecurity.setX509SignatureHashFunction (ECryptoAlgorithmSignDigest.DIGEST_SHA_256); + aPModeLegSecurity.setX509EncryptionAlgorithm (ECryptoAlgorithmCrypt.AES_128_GCM); + aPModeLegSecurity.setX509EncryptionMinimumStrength (128); + aPModeLegSecurity.setPModeAuthorize (false); + aPModeLegSecurity.setSendReceipt (true); + aPModeLegSecurity.setSendReceiptNonRepudiation (true); + aPModeLegSecurity.setSendReceiptReplyPattern (EPModeSendReceiptReplyPattern.RESPONSE); + return aPModeLegSecurity; + } + + /** + * Generate PMode leg security for the Alternative Elliptic Curve Profile (ECDSA/ECDH-ES with + * secp256r1). + * + * @return Never null. + */ + @NonNull + public static PModeLegSecurity generatePModeLegSecurityECDSA () + { + final PModeLegSecurity aPModeLegSecurity = new PModeLegSecurity (); + aPModeLegSecurity.setWSSVersion (EWSSVersion.WSS_111); + aPModeLegSecurity.setX509SignatureAlgorithm (ECryptoAlgorithmSign.ECDSA_SHA_256); + aPModeLegSecurity.setX509SignatureHashFunction (ECryptoAlgorithmSignDigest.DIGEST_SHA_256); + aPModeLegSecurity.setX509EncryptionAlgorithm (ECryptoAlgorithmCrypt.AES_128_GCM); + aPModeLegSecurity.setX509EncryptionMinimumStrength (128); + aPModeLegSecurity.setPModeAuthorize (false); + aPModeLegSecurity.setSendReceipt (true); + aPModeLegSecurity.setSendReceiptNonRepudiation (true); + aPModeLegSecurity.setSendReceiptReplyPattern (EPModeSendReceiptReplyPattern.RESPONSE); + return aPModeLegSecurity; + } + + @NonNull + public static PModeLeg generatePModeLeg (@Nullable final String sResponderAddress, + @NonNull final PModeLegSecurity aSecurity) + { + return new PModeLeg (generatePModeLegProtocol (sResponderAddress), + generatePModeLegBusinessInformation (), + generatePModeLegErrorHandling (), + (PModeLegReliability) null, + aSecurity); + } + + @NonNull + public static PModeReceptionAwareness generatePModeReceptionAwareness () + { + final ETriState eReceptionAwareness = ETriState.TRUE; + final ETriState eRetry = ETriState.TRUE; + final int nMaxRetries = 1; + final long nRetryIntervalMS = 10_000; + final ETriState eDuplicateDetection = ETriState.TRUE; + return new PModeReceptionAwareness (eReceptionAwareness, + eRetry, + nMaxRetries, + nRetryIntervalMS, + eDuplicateDetection); + } + + /** + * One-Way Version of the eDelivery 2.0 pmode uses one-way push + * + * @param sInitiatorID + * Initiator ID + * @param sResponderID + * Responder ID + * @param sResponderAddress + * Responder URL + * @param aPModeIDProvider + * PMode ID provider + * @param bPersist + * true to persist the PMode in the PModeManager, false to have + * it only in memory. + * @param aSecurity + * The security settings to use (EdDSA or ECDSA). May not be null. + * @return New PMode + */ + @NonNull + public static PMode createEDelivery2PMode (@NonNull @Nonempty final String sInitiatorID, + @NonNull @Nonempty final String sResponderID, + @Nullable final String sResponderAddress, + @NonNull final IPModeIDProvider aPModeIDProvider, + final boolean bPersist, + @NonNull final PModeLegSecurity aSecurity) + { + final PModeParty aInitiator = PModeParty.createSimple (sInitiatorID, CAS4.DEFAULT_INITIATOR_URL); + final PModeParty aResponder = PModeParty.createSimple (sResponderID, CAS4.DEFAULT_RESPONDER_URL); + + final PMode aPMode = new PMode (aPModeIDProvider.getPModeID (aInitiator, aResponder), + aInitiator, + aResponder, + DEFAULT_AGREEMENT_ID, + EMEP.ONE_WAY, + EMEPBinding.PUSH, + generatePModeLeg (sResponderAddress, aSecurity), + (PModeLeg) null, + (PModePayloadService) null, + generatePModeReceptionAwareness ()); + + if (bPersist) + { + MetaAS4Manager.getPModeMgr ().createOrUpdatePMode (aPMode); + } + return aPMode; + } + + /** + * Two-Way Version of the eDelivery 2.0 pmode uses two-way push-push + * + * @param sInitiatorID + * Initiator ID + * @param sResponderID + * Responder ID + * @param sResponderAddress + * Responder URL + * @param aPModeIDProvider + * PMode ID provider + * @param bPersist + * true to persist the PMode false to have it only in memory. + * @param aSecurity + * The security settings to use (EdDSA or ECDSA). May not be null. + * @return New PMode + */ + @NonNull + public static PMode createEDelivery2PModeTwoWay (@NonNull @Nonempty final String sInitiatorID, + @NonNull @Nonempty final String sResponderID, + @Nullable final String sResponderAddress, + @NonNull final IPModeIDProvider aPModeIDProvider, + final boolean bPersist, + @NonNull final PModeLegSecurity aSecurity) + { + final PModeParty aInitiator = PModeParty.createSimple (sInitiatorID, CAS4.DEFAULT_INITIATOR_URL); + final PModeParty aResponder = PModeParty.createSimple (sResponderID, CAS4.DEFAULT_RESPONDER_URL); + + final PMode aPMode = new PMode (aPModeIDProvider.getPModeID (aInitiator, aResponder), + aInitiator, + aResponder, + DEFAULT_AGREEMENT_ID, + EMEP.TWO_WAY, + EMEPBinding.PUSH_PUSH, + generatePModeLeg (sResponderAddress, aSecurity), + generatePModeLeg (sResponderAddress, aSecurity), + (PModePayloadService) null, + PModeReceptionAwareness.createDefault ()); + if (bPersist) + { + MetaAS4Manager.getPModeMgr ().createOrUpdatePMode (aPMode); + } + return aPMode; + } +} diff --git a/phase4-profile-edelivery2/src/main/resources/LICENSE b/phase4-profile-edelivery2/src/main/resources/LICENSE new file mode 100644 index 000000000..8f71f43fe --- /dev/null +++ b/phase4-profile-edelivery2/src/main/resources/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/phase4-profile-edelivery2/src/main/resources/META-INF/services/com.helger.phase4.profile.IAS4ProfileRegistrarSPI b/phase4-profile-edelivery2/src/main/resources/META-INF/services/com.helger.phase4.profile.IAS4ProfileRegistrarSPI new file mode 100644 index 000000000..0195aa9ba --- /dev/null +++ b/phase4-profile-edelivery2/src/main/resources/META-INF/services/com.helger.phase4.profile.IAS4ProfileRegistrarSPI @@ -0,0 +1 @@ +com.helger.phase4.profile.edelivery2.AS4EDelivery2ProfileRegistarSPI diff --git a/phase4-profile-edelivery2/src/main/resources/NOTICE b/phase4-profile-edelivery2/src/main/resources/NOTICE new file mode 100644 index 000000000..6b30bbfce --- /dev/null +++ b/phase4-profile-edelivery2/src/main/resources/NOTICE @@ -0,0 +1,5 @@ +============================================================================= += NOTICE file corresponding to section 4d of the Apache License Version 2.0 = +============================================================================= +This product includes Open Source Software developed by +Philip Helger - https://www.helger.com/ diff --git a/phase4-profile-edelivery2/src/test/java/com/helger/phase4/profile/edelivery2/EDelivery2CompatibilityValidatorTest.java b/phase4-profile-edelivery2/src/test/java/com/helger/phase4/profile/edelivery2/EDelivery2CompatibilityValidatorTest.java new file mode 100644 index 000000000..3d27ee7fa --- /dev/null +++ b/phase4-profile-edelivery2/src/test/java/com/helger/phase4/profile/edelivery2/EDelivery2CompatibilityValidatorTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.profile.edelivery2; + +import static org.junit.Assert.assertTrue; + +import org.junit.ClassRule; +import org.junit.Test; + +import com.helger.diagnostics.error.list.ErrorList; +import com.helger.phase4.model.pmode.IPModeIDProvider; +import com.helger.phase4.model.pmode.PMode; +import com.helger.phase4.profile.IAS4ProfileValidator; +import com.helger.photon.app.mock.PhotonAppTestRule; + +/** + * Test class for class {@link EDelivery2CompatibilityValidator}. + * + * @author Philip Helger + */ +public final class EDelivery2CompatibilityValidatorTest +{ + @ClassRule + public static final PhotonAppTestRule RULE = new PhotonAppTestRule (); + + @Test + public void testValidEdDSAPMode () + { + final PMode aPMode = EDelivery2PMode.createEDelivery2PMode ("initiator", + "responder", + "https://test.example.com", + IPModeIDProvider.DEFAULT_DYNAMIC, + false, + EDelivery2PMode.generatePModeLegSecurityEdDSA ()); + final IAS4ProfileValidator aValidator = new EDelivery2CompatibilityValidator ().setExpectFourCornerModel (false); + final ErrorList aErrorList = new ErrorList (); + aValidator.validatePMode (aPMode, + aErrorList, + IAS4ProfileValidator.EAS4ProfileValidationMode.USER_MESSAGE); + assertTrue ("Errors: " + aErrorList.toString (), aErrorList.containsNoError ()); + } + + @Test + public void testValidECDSAPMode () + { + final PMode aPMode = EDelivery2PMode.createEDelivery2PMode ("initiator", + "responder", + "https://test.example.com", + IPModeIDProvider.DEFAULT_DYNAMIC, + false, + EDelivery2PMode.generatePModeLegSecurityECDSA ()); + final IAS4ProfileValidator aValidator = new EDelivery2CompatibilityValidator ().setExpectFourCornerModel (false) + .setAllowECDSA (true); + final ErrorList aErrorList = new ErrorList (); + aValidator.validatePMode (aPMode, + aErrorList, + IAS4ProfileValidator.EAS4ProfileValidationMode.USER_MESSAGE); + assertTrue ("Errors: " + aErrorList.toString (), aErrorList.containsNoError ()); + } + + @Test + public void testECDSANotAllowedByDefault () + { + final PMode aPMode = EDelivery2PMode.createEDelivery2PMode ("initiator", + "responder", + "https://test.example.com", + IPModeIDProvider.DEFAULT_DYNAMIC, + false, + EDelivery2PMode.generatePModeLegSecurityECDSA ()); + // Default validator does NOT allow ECDSA + final IAS4ProfileValidator aValidator = new EDelivery2CompatibilityValidator ().setExpectFourCornerModel (false) + .setAllowECDSA (false); + final ErrorList aErrorList = new ErrorList (); + aValidator.validatePMode (aPMode, + aErrorList, + IAS4ProfileValidator.EAS4ProfileValidationMode.USER_MESSAGE); + // Should have error about signature algorithm + assertTrue ("Expected errors for ECDSA when not allowed", aErrorList.containsAtLeastOneError ()); + } + + @Test + public void testValidTwoWayPMode () + { + final PMode aPMode = EDelivery2PMode.createEDelivery2PModeTwoWay ("initiator", + "responder", + "https://test.example.com", + IPModeIDProvider.DEFAULT_DYNAMIC, + false, + EDelivery2PMode.generatePModeLegSecurityEdDSA ()); + final IAS4ProfileValidator aValidator = new EDelivery2CompatibilityValidator ().setExpectFourCornerModel (false); + final ErrorList aErrorList = new ErrorList (); + aValidator.validatePMode (aPMode, + aErrorList, + IAS4ProfileValidator.EAS4ProfileValidationMode.USER_MESSAGE); + assertTrue ("Errors: " + aErrorList.toString (), aErrorList.containsNoError ()); + } +} diff --git a/phase4-profile-edelivery2/src/test/java/com/helger/phase4/profile/edelivery2/SPITest.java b/phase4-profile-edelivery2/src/test/java/com/helger/phase4/profile/edelivery2/SPITest.java new file mode 100644 index 000000000..8d44b24ce --- /dev/null +++ b/phase4-profile-edelivery2/src/test/java/com/helger/phase4/profile/edelivery2/SPITest.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.phase4.profile.edelivery2; + +import org.junit.Test; + +import com.helger.unittestext.SPITestHelper; + +/** + * Test SPI definitions + * + * @author Philip Helger + */ +public final class SPITest +{ + @Test + public void testBasic () throws Exception + { + SPITestHelper.testIfAllSPIImplementationsAreValid (); + } +} diff --git a/pom.xml b/pom.xml index f74a2b1fa..17673bd0c 100644 --- a/pom.xml +++ b/pom.xml @@ -245,6 +245,18 @@ phase4-cef-client ${project.version} + + + + com.helger.phase4 + phase4-profile-edelivery2 + ${project.version} + + + com.helger.phase4 + phase4-edelivery2-client + ${project.version} + @@ -335,6 +347,7 @@ phase4-profile-bdew phase4-profile-cef + phase4-profile-edelivery2 phase4-profile-dbnalliance phase4-profile-entsog phase4-profile-euctp @@ -350,7 +363,9 @@ phase4-bdew-client phase4-cef-client - + + phase4-edelivery2-client + phase4-dbnalliance-client phase4-dbnalliance-servlet phase4-dbnalliance-server-webapp From fb72542a217544fb6840e229b96eb67c4894caaf Mon Sep 17 00:00:00 2001 From: Philip Helger Date: Thu, 19 Mar 2026 11:03:58 +0100 Subject: [PATCH 6/6] Removed unused version --- phase4-edelivery2-client/pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/phase4-edelivery2-client/pom.xml b/phase4-edelivery2-client/pom.xml index 70426d80d..a6560b847 100644 --- a/phase4-edelivery2-client/pom.xml +++ b/phase4-edelivery2-client/pom.xml @@ -47,7 +47,6 @@ com.helger.phase4 phase4-profile-edelivery2 - ${project.version} com.helger.phase4