Skip to content

AS4 Peppol

Purpose

Peppol AS4 Messaging Support

This DSL is based on the open source library Phase4: https://github.com/phax/phase4

See the folder https://github.com/phax/phase4/tree/master/phase4-peppol-client/src/test/java/com/helger/phase4/peppol for different examples on how to send messages via the Peppol AS4 client.

Warning

p6core 6.9.0 and above uses Java 17 internally. This has a know security constraint/issue: https://github.com/phax/phase4/wiki/Known-Limitations To work around this constraint requires the creation of the file $P6_DATA/conf/security/java.security (see examples)

Methods

Binding name: p6.as4peppol

Security recommandations

Since 6.9.0

Recommended java.security file for p6core

jdk.xml.dsig.secureValidationPolicy=\
    disallowAlg http://www.w3.org/TR/1999/REC-xslt-19991116,\
    disallowAlg http://www.w3.org/2001/04/xmldsig-more#rsa-md5,\
    disallowAlg http://www.w3.org/2001/04/xmldsig-more#hmac-md5,\
    disallowAlg http://www.w3.org/2001/04/xmldsig-more#md5,\
    disallowAlg http://www.w3.org/2000/09/xmldsig#dsa-sha1,\
    disallowAlg http://www.w3.org/2007/05/xmldsig-more#sha1-rsa-MGF1,\
    disallowAlg http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1,\
    maxTransforms 5,\
    maxReferences 30,\
    disallowReferenceUriSchemes file http https,\
    minKeySize RSA 1024,\
    minKeySize DSA 1024,\
    minKeySize EC 224,\
    noDuplicateIds,\
    noRetrievalMethodLoops
Logging, Debug & Testing

The configuration properties global.production and global.debug are used to switch between Test and Production certificate usage and control the generation of DEBUG logs

In Addition, the following line should be added to the log4j.properties file to enable DEBUG logging from the Phase4 library:

log4j.logger.com.helger.phase4=DEBUG

buildContextConfiguration

Syntax

AutoCloseable p6.as4peppol.buildContextConfiguration(Map<String, String> configuration)

Phase4 is designed to gather configuration from properties files provided at runtime. This does not suite the platform6 execution architecture hence the introduction of the context configuration

Context configuration replaces the use of properties files with a Map[:] of configuration attributes defined at runtime.

It is therefore essential that all calls to the DSL are made within the configuration context and the context is closed at the end of execution.

This method returns an AutoClosable making the lifecycle of the context easy to control in Groovy:

Parameter: configuration

To reduce the configuration required to build a context each time, many of the Phase4 required properties have been defaults so only overrides are required. The defaults are:

Key Default Value
phase4.wss4j.syncsecurity “true”
global.debug “false”
global.production “true”
global.nostartupinfo “true”
global.datapath P6_DATA + “/resources/as4”
phase4.dump.path P6_DATA + “/resources/as4/dump”
org.apache.wss4j.crypto.provider “org.apache.wss4j.common.crypto.Merlin”
org.apache.wss4j.crypto.merlin.keystore.type “pkcs12”
org.apache.wss4j.crypto.merlin.keystore.file P6_DATA + “/resources/as4/as4peppol-ap.p12”
org.apache.wss4j.crypto.merlin.keystore.password “peppol”
org.apache.wss4j.crypto.merlin.keystore.provider “BC”
org.apache.wss4j.crypto.merlin.keystore.alias “cert”
org.apache.wss4j.crypto.merlin.load.cacerts “false”
org.apache.wss4j.crypto.merlin.truststore.type “jks”
org.apache.wss4j.crypto.merlin.truststore.file P6_DATA + “/resources/as4/as4peppol-complete-truststore.jks”
org.apache.wss4j.crypto.merlin.truststore.password “peppol”
org.apache.wss4j.crypto.merlin.keystore.private.password” same as org.apache.wss4j.crypto.merlin.keystore.password
Example
p6.as4peppol.buildContextConfiguration( configMap ).withCloseable {

    // Context aware DSL calls here...

}

buildHttpSettings

Create a Phase4 HttpClientSettings object from a given SSLContext. This allows the p6.securesocket DSL to be used to define the HttpClient attributes when POSTing AS4

Syntax

HttpClientSettings p6.as4peppol.buildHttpSettings(SSLContext ctx)
Example
def ctx = p6.securesocket.contextBuilder()
            .setType( SecureContext.BundleType.ONE_WAY_TRUST_ANY )
            .setProtocol( "TLSv1.2" )
            .build();

def httpSettings = p6.as4peppol.buildHttpSettings( p6.securesocket.contextBuild( ctx ) )

sendBuilder

Create a new Builder for AS4 messages if the payload is present and the SBDH (openPEPPOL Business Message Envelope) should be created internally

Syntax

Phase4PeppolSender.Builder p6.as4peppol.sendBuilder()
Example: HTTP openPEPPOL AS4 Send with Receipt checker

The following is an example of sending a UBL Invoice document (XML defined via script resource) to the Austrian Governments test endpoint:

import groovy.xml.DOMBuilder

import com.helger.peppol.sml.ESML
import com.helger.smpclient.peppol.SMPClientReadOnly
import com.helger.phase4.dump.AS4RawResponseConsumerWriteToFile

def xml = p6.resource.get( "UBLInvoice" )
def payloadElement = DOMBuilder.parse(new StringReader(xml)).getDocumentElement()

// Austrian Government id
def receiverID = p6.as4peppol.idFactory().createParticipantIdentifierWithDefaultScheme ( "9915:test" )

def configMap = [:]

configMap["global.debug"] = "true"
configMap["global.production"] = "false"
configMap["org.apache.wss4j.crypto.merlin.keystore.password"] = "password-in-here"

p6.as4peppol.buildContextConfiguration( configMap ).withCloseable {

    def ctx = p6.securesocket.contextBuilder()
            .setType( SecureContext.BundleType.ONE_WAY_TRUST_ANY )
            .setProtocol( "TLSv1.2" )
            .build();

    def httpSettings = p6.as4peppol.buildHttpSettings( p6.securesocket.contextBuild( ctx ) )

    def result = p6.as4peppol.sendBuilder()
            .documentTypeID ( p6.as4peppol.idFactory().createDocumentTypeIdentifierWithDefaultScheme ("urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1") )
            .processID ( p6.as4peppol.idFactory().createProcessIdentifierWithDefaultScheme ("urn:fdc:peppol.eu:2017:poacc:billing:01:1.0") )
            .senderParticipantID ( p6.as4peppol.idFactory().createParticipantIdentifierWithDefaultScheme ("9915:phase4-test-sender") )
            .receiverParticipantID ( receiverID )
            .senderPartyID ( "YOUR_POP_ID_HERE" )
            .payload ( payloadElement )
            .smpClient ( new SMPClientReadOnly (p6.as4peppol.sender.URL_PROVIDER, receiverID, ESML.DIGIT_TEST) )
            .rawResponseConsumer ( new AS4RawResponseConsumerWriteToFile () )
            .validationConfiguration ( null )
            .buildMessageCallback ( p6.as4peppol.simpleMessageSendLogger() )
            .httpClientFactory ( httpSettings )
            .sendMessageAndCheckForReceipt ()

    println result
}

sendSBDHBuilder

Create a new Builder for AS4 messages if the SBDH payload is already present

Syntax

Phase4PeppolSender.SBDHBuilder p6.as4peppol.sendSBDHBuilder()

idFactory

Simple wrapper over Phase4PeppolSender.IF to allow id generation

Syntax

PeppolIdentifierFactory p6.as4peppol.idFactory()
Example
def receiverID = p6.as4peppol.idFactory().createParticipantIdentifierWithDefaultScheme ( "9915:test" )

Simple wrapper

Simple wrapper to reference the Phase4PeppolSender class

Syntax

Class<Phase4PeppolSender> sender = Phase4PeppolSender.class

simpleMessageSendLogger

A simple implementation of IAS4ClientBuildMessageCallback to log message details during the send process

Syntax

IAS4ClientBuildMessageCallback p6.as4peppol.simpleMessageSendLogger()
Example
def logger = p6.as4peppol.simpleMessageSendLogger()

receive

Receive an openPEPPOL AS4 Message. A byte array containing the message content and a separate list of MIME headers are supplied as a Map as parameters.

Syntax

Tuple3<Integer, byte[], Map<String, String>> p6.as4peppol.receive(
    final Map<String, String> headers,
    final byte[] ba,
    final Closure<Void>... messageReceipt
)

This method takes an optional callback method to use during the receipt processing:

  • Closure - messageReceipt: is called when an inbound openPEPPOL Message is identified. The parameters passed are: filePath Path of received message, messageId: Unique id of the received message

The method returns a Tuple3 containing the following:

  • Integer: the HTTP status code to response with (200 is good)
  • String: the response entity as a byte array (typically a receipt)
  • Map: map of headers to use in a response to this request
Example

Receive HTTP POSTed AS4 openPEPPOL Message and Generate Receipt

Route Deployment Script
rest('/public')
        .post('/as4')
        .to('p6cmb://scripts?platform6.request.action=execute&id=AS4PeppolInbound')
        .id('AS4PeppolInbound')
Route Executed Script (AS4PeppolInbound)
def configMap = [:]

configMap["global.debug"] = "true";
configMap["global.production"] = "false";
configMap["org.apache.wss4j.crypto.merlin.keystore.password"] = "password-in-here"

p6.as4peppol.buildContextConfiguration( configMap ).withCloseable {

    def tpl = p6.as4peppol.receive(
            p6.pipeline.toStringMap(),
            p6.pipeline.getBytes('body'),
            { sbdFilePath, messageId -> println '++++++ RECEIVED openPEPPOL Message\n' + '\nPath: ' + sbdFilePath + '\nMessageID: ' + messageId}
    )

    // --- Build the Http POST response into the pipeline ---

    // Response code
    p6.pipeline.put('CamelHttpResponseCode', '' + tpl.first)
    // The response or error-message body
    p6.pipeline.put('body', tpl.second)
    // Additional headers - prefixed with 'p6rest.' to ensure they are part of the final HTTP response
    tpl.third.each { key, val ->
        p6.pipeline.put( 'p6rest.' + key, val )
    }
}

setPerformSBDHValueChecking

Enable value checks when reading Peppol SBDH documents (inbound message validation) default: false

Syntax

void p6.as4peppol.setPerformSBDHValueChecking(boolean performSBDHValueChecking)

setPerformReceiverChecking

Enable receiver consistency checks (inbound validation) default: false

Syntax

void p6.as4peppol.setPerformReceiverChecking(boolean performReceiverChecking)