phax / phase4

phase4 - AS4 client and server for integration into existing systems. Specific support for Peppol and CEF eDelivery built-in.
Apache License 2.0
154 stars 47 forks source link

How to enforce signature and encryption for receiving messages? #162

Closed phax closed 1 year ago

phax commented 1 year ago

Discussed in https://github.com/phax/phase4/discussions/161

Originally posted by **sopgreg** August 29, 2023 Is there a way to enforce a (valid) signature and encryption in the receiving AS4 message? Right now, it seems that it's possible to send a completely unencrypted, unsigned message to the `AS4IncomingHandler` and it will process the message without any warnings. Is there an interface/propery to enforce signatures and encryption? E.g. here, if the SOAP does not contain a `Security` element, this is just logged in debug mode. ![grafik](https://github.com/phax/phase4/assets/33895520/864c20f5-a6e7-4b48-9eec-93ff4c2ec058)
phax commented 1 year ago

@sopgreg what you can do today already, is to query the signature and decryption state in your code via the IAS4MessageState:

sopgreg commented 1 year ago

Thanks @phax! I put this in our IAS4ServletMessageProcessorSPI#processAS4UserMessage. Would that be a "correct" implementation where the sender receives the proper reason for failed processing of his message?

if (!messageState.isSoapDecrypted()) {
    processingErrorMessages.add(EEbmsError.EBMS_FAILED_DECRYPTION.getAsEbms3Error(Locale.getDefault(), null));

    String errorMessage = String.format("Incoming AS4 message from %s to %s was not encrypted",
                                        initiatorID, responderID);
    LOGGER.error(errorMessage);
    return AS4MessageProcessorResult.createFailure(errorMessage);
}

if (!messageState.isSoapSignatureChecked()) {
    processingErrorMessages.add(EEbmsError.EBMS_FAILED_AUTHENTICATION.getAsEbms3Error(Locale.getDefault(), null));

    String errorMessage = String.format("Incoming AS4 message from %s to %s had no signature or signature verification failed",
                                        initiatorID, responderID);
    LOGGER.error(errorMessage);
    return AS4MessageProcessorResult.createFailure(errorMessage);
}

The usage of both the processingErrorMessages and the error message in createFailure seems a bit redundant to me. Is there a variant you would recommend?

phax commented 1 year ago

Yes that looks good. I am working on an additional "profile requirement" to make that check on a deeper level automatically as you suggested. Additionally your comment on AS4MessageProcessorResult.createFailure(errorMessage); is totally right. As this messgae is never used, it will be also be deprecated in the upcoming release. processingErrorMessages is the way to go

phax commented 1 year ago

Regarding the error code I would vouch for EBMS:0103: grafik

sopgreg commented 1 year ago

Regarding the error code I would vouch for EBMS:0103:

EBMS:0103 sounds fine, thanks.

Additionally your comment on AS4MessageProcessorResult.createFailure(errorMessage); is totally right. As this messgae is never used, it will be also be deprecated in the upcoming release. processingErrorMessages is the way to go

I actually do like the error message in AS4MessageProcessorResult.createFailure(errorMessage) as it is automatically mapped to an Ebms error with a correct refToMessageInError in AS4RequestHandler:

grafik

There is no easy way to get that refToMessageInError in the SPI, is there? Apart from copying the same code from AS4RequestHandler:

final String sMessageID = bIsUserMessage ? aEbmsUserMessage.getMessageInfo ().getMessageId () : aEbmsSignalMessage
                                                                                                                      .getMessageInfo ()
                                                                                                                      .getMessageId ();

EDIT: seems that messageState.getMessage() seems to be the way to go.

Yes that looks good. I am working on an additional "profile requirement" to make that check on a deeper level automatically as you suggested.

Great if this can be handled on a deeper level, then we could remove our code in the SPI again later.

phax commented 1 year ago

To access the "RefToMessageID" please use

String IAS4MessageState.getRefToMessageID ();

The problem with the createFailure(String) is, that it allows only 1 error message. In some cases multiple error messages make sense. And as I consider it bad style to offer the same functionality in 2 different ways, I opt for the generic one using processingErrorMessages.add (...)

phax commented 1 year ago

@sopgreg I thought this through a little more and I stumbled upon an issue we haven't considered previously: when making the decision in the profile, it means that this applies to ALL messages. As Receipts may not be encrypted and Error Messages may not even be signed, I decided to remove this feature from the release. Having the check in your "User Message Receiption" code is the safest atm.

It would be an option to configure this requirement in the PMode on a per message type basis (UserMessage, PullRequest, Error, Receipt), but that looks like too much overhead. Any thoughts from you on this?

sopgreg commented 1 year ago

I also can't image all possible combinations right now when and under which circumstances a response message may or may not be signed/encrypted. At least for BDEW, PullRequests are out of scope. So any UserMessage and all signal that do not contain errors should be technically signed and encrypted. Is this assumption correct or did I miss some edge cases?

To access the "RefToMessageID" please use String IAS4MessageState.getRefToMessageID ();

In the code you committed today you used #getMessageId. Was this on purpose or is getRefToMessageId the correct one?

phax commented 1 year ago

Okay, thanks for your understanding.

getMessageId() is used to get the message ID of the currently processed message.

getRefToMessageId() is used in Receipt or Error Messages to refence the MessageId of the source message. So the answer message referring to the source message.

When dealing with the "source user message" getMessageId() is what you are looking for