Skip to content

Commit

Permalink
IAS4ProfileValidator: validate if initiator party ID, signature certi…
Browse files Browse the repository at this point in the history
…ficate and TLS client certificate match phax#182
  • Loading branch information
koes-soptim committed Oct 17, 2023
1 parent 6b25d53 commit 8bce054
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package com.helger.phase4.messaging;

import java.security.cert.X509Certificate;
import java.time.OffsetDateTime;

import javax.annotation.CheckForSigned;
Expand Down Expand Up @@ -152,6 +153,25 @@ default boolean hasRemoteUser ()
return StringHelper.hasText (getRemoteUser ());
}

/**
* Returns the TLS certificates presented by the remote client to
* authenticate itself.
*
* @return an array containing a chain of X509Certificate objects
*/
@Nullable
X509Certificate[] getRemoteTlsCerts ();

/**
* @return <code>true</code> if the remote TLS certificate chain with at least
* a single certificate is present, <code>false</code> if not.
* @see #getRemoteUser()
*/
default boolean hasRemoteTlsCerts ()
{
return getRemoteTlsCerts () != null && getRemoteTlsCerts ().length > 0;
}

/**
* @return A list of all Cookies contained in the request. Never
* <code>null</code> but maybe empty. The returned list is mutable so
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@
package com.helger.phase4.profile;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.helger.commons.error.list.ErrorList;
import com.helger.phase4.ebms3header.Ebms3SignalMessage;
import com.helger.phase4.ebms3header.Ebms3UserMessage;
import com.helger.phase4.messaging.IAS4IncomingMessageMetadata;
import com.helger.phase4.model.pmode.IPMode;
import com.helger.phase4.v3.ChangeV3;

import java.security.cert.X509Certificate;

/**
* Generic AS4 profile validator
*
Expand All @@ -44,6 +48,24 @@ public interface IAS4ProfileValidator
default void validatePMode (@Nonnull final IPMode aPMode, @Nonnull final ErrorList aErrorList)
{}

/**
* Validation method
*
* @param aUserMsg
* The message to use for comparison. May not be <code>null</code>.
* @param aSigCert
* The signature certificate used to sign the message. Can be <code>null</code>.
* @param aMessageMetadata
* Metadata of the message containing the TLS client certificate. May not be <code>null</code>.
* @param aErrorList
* The error list to be filled. May not be <code>null</code>.
*/
default void validateInitiatorIdentity(@Nonnull final Ebms3UserMessage aUserMsg,
@Nullable X509Certificate aSigCert,
@Nonnull IAS4IncomingMessageMetadata aMessageMetadata,
@Nonnull final ErrorList aErrorList)
{}

/**
* Validation method
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,8 @@ public static IAS4MessageState processEbmsMessage (@Nonnull @WillNotClose final
@Nonnull final ESoapVersion eSoapVersion,
@Nonnull final ICommonsList <WSS4JAttachment> aIncomingAttachments,
@Nonnull final IAS4IncomingProfileSelector aAS4ProfileSelector,
@Nonnull final ICommonsList <Ebms3Error> aErrorMessagesTarget) throws Phase4Exception
@Nonnull final ICommonsList <Ebms3Error> aErrorMessagesTarget,
@Nonnull final IAS4IncomingMessageMetadata aMessageMetadata) throws Phase4Exception
{
ValueEnforcer.notNull (aResHelper, "ResHelper");
ValueEnforcer.notNull (aLocale, "Locale");
Expand Down Expand Up @@ -691,6 +692,7 @@ public static IAS4MessageState processEbmsMessage (@Nonnull @WillNotClose final
final ErrorList aErrorList = new ErrorList ();
aValidator.validatePMode (aPMode, aErrorList);
aValidator.validateUserMessage (aEbmsUserMessage, aErrorList);
aValidator.validateInitiatorIdentity(aEbmsUserMessage, aState.getUsedCertificate(), aMessageMetadata, aErrorList);
if (aErrorList.isNotEmpty ())
{
throw new Phase4Exception ("Error validating incoming AS4 UserMessage with the profile " +
Expand Down Expand Up @@ -811,7 +813,8 @@ private static IAS4MessageState _parseMessage (@Nonnull final IAS4CryptoFactory
eSoapVersion,
aIncomingAttachments,
aAS4ProfileSelector,
aErrorMessages);
aErrorMessages,
aMessageMetadata);

if (aState.isSoapHeaderElementProcessingSuccessful ())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package com.helger.phase4.servlet;

import java.security.cert.X509Certificate;
import java.time.OffsetDateTime;
import java.util.UUID;

Expand Down Expand Up @@ -51,6 +52,7 @@ public class AS4IncomingMessageMetadata implements IAS4IncomingMessageMetadata
private String m_sRemoteHost;
private int m_nRemotePort = -1;
private String m_sRemoteUser;
private X509Certificate[] m_aRemoteTlsCerts;
private final ICommonsList <Cookie> m_aCookies = new CommonsArrayList <> ();
private String m_sRequestMessageID;

Expand Down Expand Up @@ -189,6 +191,26 @@ public AS4IncomingMessageMetadata setRemoteUser (@Nullable final String sRemoteU
return this;
}

@Nullable
public X509Certificate[] getRemoteTlsCerts ()
{
return m_aRemoteTlsCerts;
}

/**
* Set the remote TLS certificates to be used.
*
* @param aRemoteTlsCerts
* The TLS certificates the remote client presented during the handshake. May be <code>null</code>.
* @return this for chaining
*/
@Nonnull
public AS4IncomingMessageMetadata setRemoteTlsCerts (@Nullable final X509Certificate[] aRemoteTlsCerts)
{
m_aRemoteTlsCerts = aRemoteTlsCerts;
return this;
}

@Nonnull
@ReturnsMutableObject
public ICommonsList <Cookie> cookies ()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1307,7 +1307,8 @@ private IAS4ResponseFactory _handleSoapMessage (@Nonnull final HttpHeaderMap aHt
eSoapVersion,
aIncomingAttachments,
m_aIncomingProfileSelector,
aErrorMessagesTarget);
aErrorMessagesTarget,
m_aMessageMetadata);

// Evaluate the results of processing
final IPMode aPMode = aState.getPMode ();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package com.helger.phase4.servlet;

import java.security.cert.X509Certificate;
import java.util.function.Supplier;

import javax.annotation.Nonnull;
Expand Down Expand Up @@ -339,14 +340,16 @@ public AS4UnifiedResponse createUnifiedResponse (@Nonnull final EHttpVersion eHT
*/
@Nonnull
@OverrideOnDemand
protected AS4IncomingMessageMetadata createIncomingMessageMetadata (@Nonnull final IRequestWebScopeWithoutResponse aRequestScope)
protected AS4IncomingMessageMetadata createIncomingMessageMetadata (@Nonnull final IRequestWebScopeWithoutResponse aRequestScope,
@Nullable final X509Certificate[] aClientTlsCerts)
{
return AS4IncomingMessageMetadata.createForRequest ()
.setRemoteAddr (aRequestScope.getRemoteAddr ())
.setRemoteHost (aRequestScope.getRemoteHost ())
.setRemotePort (aRequestScope.getRemotePort ())
.setRemoteUser (aRequestScope.getRemoteUser ())
.setCookies (aRequestScope.getCookies ());
.setCookies (aRequestScope.getCookies ())
.setRemoteTlsCerts (aClientTlsCerts);
}

/**
Expand Down Expand Up @@ -392,8 +395,18 @@ protected void handleRequest (@Nonnull final IRequestWebScopeWithoutResponse aRe
@Nonnull final IAS4IncomingSecurityConfiguration aISC,
@Nullable final IHandlerCustomizer aHandlerCustomizer) throws Exception
{
X509Certificate[] aClientTlsCerts = null;
try
{
aClientTlsCerts = (X509Certificate[]) aRequestScope.getRequest ()
.getAttribute ("jakarta.servlet.request.X509Certificate");
} catch (final Exception ex)
{
LOGGER.info ("No client TLS certificate provided");
}

// Start metadata
final IAS4IncomingMessageMetadata aMessageMetadata = createIncomingMessageMetadata (aRequestScope);
final IAS4IncomingMessageMetadata aMessageMetadata = createIncomingMessageMetadata (aRequestScope, aClientTlsCerts);

try (final AS4RequestHandler aHandler = new AS4RequestHandler (aCryptoFactorySign,
aCryptoFactoryCrypt,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.helger.phase4.profile.bdew;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.Nonempty;
Expand All @@ -30,9 +31,12 @@
import com.helger.phase4.crypto.ECryptoAlgorithmSignDigest;
import com.helger.phase4.ebms3header.Ebms3AgreementRef;
import com.helger.phase4.ebms3header.Ebms3From;
import com.helger.phase4.ebms3header.Ebms3PartyId;
import com.helger.phase4.ebms3header.Ebms3PartyInfo;
import com.helger.phase4.ebms3header.Ebms3SignalMessage;
import com.helger.phase4.ebms3header.Ebms3To;
import com.helger.phase4.ebms3header.Ebms3UserMessage;
import com.helger.phase4.messaging.IAS4IncomingMessageMetadata;
import com.helger.phase4.mgr.MetaAS4Manager;
import com.helger.phase4.model.EMEP;
import com.helger.phase4.model.EMEPBinding;
Expand All @@ -47,6 +51,12 @@
import com.helger.phase4.profile.IAS4ProfileValidator;
import com.helger.phase4.soap.ESoapVersion;
import com.helger.phase4.wss.EWSSVersion;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x500.style.IETFUtils;

import java.security.cert.X509Certificate;

/**
* Validate certain requirements imposed by the BDEW project.
Expand Down Expand Up @@ -338,6 +348,59 @@ public void validatePMode (@Nonnull final IPMode aPMode, @Nonnull final ErrorLis
}
}

@Override
public void validateInitiatorIdentity(@Nonnull final Ebms3UserMessage aUserMsg,
@Nullable final X509Certificate aSignatureCert,
@Nonnull final IAS4IncomingMessageMetadata aMessageMetadata,
@Nonnull ErrorList aErrorList)
{
final Ebms3PartyInfo aIniatorPartyInfo = aUserMsg.getPartyInfo();

if (aIniatorPartyInfo != null)
{
Ebms3From aInitiator = aIniatorPartyInfo.getFrom();

if (aInitiator != null)
{
Ebms3PartyId aInitiatorPartyId = aInitiator.getPartyIdAtIndex(0);

if (aInitiatorPartyId != null)
{
String sInitiatorId = aInitiatorPartyId.getValue();

if (sInitiatorId != null)
{
if (aSignatureCert != null)
{
X500Name aSigName = new X500Name(aSignatureCert.getSubjectX500Principal().getName());
RDN aSigOuRDN = aSigName.getRDNs(BCStyle.OU)[0];
String sSigCertId = IETFUtils.valueToString(aSigOuRDN.getFirst().getValue());

if (!sInitiatorId.equals(sSigCertId))
{
aErrorList.add (_createError ("ID of initiator party does not match ID in signature certificate"));
}
}

if (aMessageMetadata.getRemoteTlsCerts() != null && aMessageMetadata.getRemoteTlsCerts().length > 0)
{
X509Certificate aTlsClientEndCert = aMessageMetadata.getRemoteTlsCerts()[0];

X500Name aTlsName = new X500Name(aTlsClientEndCert.getSubjectX500Principal().getName());
RDN aTlsOuRDN = aTlsName.getRDNs(BCStyle.OU)[0];
String sTlsCertId = IETFUtils.valueToString(aTlsOuRDN.getFirst().getValue());

if (!sInitiatorId.equals (sTlsCertId))
{
aErrorList.add (_createError ("ID of initiator party does not match ID in client TLS certificate"));
}
}
}
}
}
}
}

@Override
public void validateUserMessage (@Nonnull final Ebms3UserMessage aUserMsg, @Nonnull final ErrorList aErrorList)
{
Expand Down

0 comments on commit 8bce054

Please sign in to comment.