Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IAS4ProfileValidator: new validation, relates to #182 #183

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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