Search code examples
testingedi

EDI AS2 HTTP trace?


We're looking into an AS2 implementation and would like to be able to build meaningful test-cases to be used with SoapUI or Postman. In order to do this we have two ways:

  1. just try to tcp-dump/trace calls from an existing client
  2. manually build some simple calls starting from the plain EDI documents

or various reasons we've discarded (1) so we must go with (2) and we need some documentation. This doc from Oracle is a good starting point: https://docs.oracle.com/cd/E19398-01/820-1228/agfat/index.html

but we cannot really find a step-by-step guide for building the AS2 request starting from the ORDERS edi document (which we already have).

Ideally I'd like a step-by-step guide which says something like:

  1. generate a private cert: +commandline
  2. encrypt the EDI document: +commandline +sampleoutput
  3. create signature: +commandline +sampleoutput
  4. build the S/MIME package: +commandline +sampleoutput
  5. send over HTTP/S: +commandline +samplehttptrace

Solution

  • Sending Message

    To send a message we need a file with message to send, two pairs of keys for both sides (each partner has one pair of keys for signing and another pair of keys for encrypting, each pair of keys consists of public and private keys). For this tutorial we will use the same pair of keys for signing and encryption per partner.

    Generate Keys

    Using OpenSSL create two pairs of keys:

    openssl req -x509 -newkey rsa:2048 -keyout P1_private.pem -out P1_public.pem -days 365
    

    The same for second pair:

    openssl req -x509 -newkey rsa:2048 -keyout P2_private.pem -out P2_public.pem -days 365
    

    Extend Private Key Files

    P1_private.pem and P2_private.pem contain only private keys. OpenSSL supports extended PEM format where both, private and public keys, can be in one file. This simplifies some OpenSSL commands (one file can be given instead of two). This tutorial assumes that this has been done:

    cat P1_public.pem >> P1_private.pem
    
    cat P2_public.pem >> P2_private.pem
    

    Otherwise, check OpenSSL commands manual to see how to specify second file where needed.

    Sign File

    Let's assume we want to send GETMSG.edi file via AS2 protocol from P1 partner => to P2, with signing and encryption. First we add appropriate MIME headers to it:

    Content-Type: application/edi-consent
    Content-Disposition: attachment; filename="GETMSG.edi"
    
    UNA:+./*'
    UIB+UNOA:0++2289+++77777777:C:PASSWORDA+111111:M+19971001:074620'
    UIH+SCRIPT:010:006:GETMSG+111'
    UIT+111+2'
    UIZ++1'
    

    Save new file as GETMSG.msg

    Then sign the file with private key of sending partner P1:

    openssl smime -sign -in GETMSG.msg -out GETMSG_SIGNED.msg -signer P1_private.pem
    

    Note: OpenSSL puts old MIME types that start with 'x-pkcs7-', some applications (pyAS2) can handle only new MIME types without eks 'pkcs7-'. Just remove 'x-' everywhere from resulting file.

    Encrypt

    Now we have multipart attachment with first part being the file content and the second part is the signature. Now we encrypt it with public key of receiving partner P2:

    openssl smime -encrypt -in GETMSG_SIGNED.msg -out GETMSG_ENC.msg -des3 P2_public.pem
    

    Resulting file GETMSG_ENC.msg can be send now to recipient as request with additional AS2 headers.

    Note: OpenSSL puts old MIME types that start with 'x-pkcs7-', some applications (pyAS2) can handle only new MIME types without eks 'pkcs7-'. Just remove 'x-' everywhere from resulting file.

    Send via cURL

    To send with cURL we have to separate headers and body of POST request. Remove from file GETMSG_ENC.msg all headers (it shoud start with 'MI..'). Use this command to send it from P1 (AS2 ID: p1as2) to P2 (AS2 ID: p2as2), assuming that P2 URL is "http://localhost:8080/pyas2/as2receive":

    set NOW=%DATE:~10,4%%DATE:~4,2%%DATE:~7,2%%TIME:~0,2%%TIME:~3,2%%TIME:~6,2%
    
    curl -i -X POST-H "Content-Disposition: attachment; filename=\"smime.p7m\""-H "Content-Type: application/pkcs7-mime; smime-type=enveloped-data; name=\"smime.p7m\""-H "Content-Transfer-Encoding: base64"-H "AS2-TO: p1as2"-H "AS2-FROM: p2as2"-H "AS2-VERSION: 1.2"-H "MESSAGE-ID: <openssl%NOW%@LOCALHOST>"-H "Disposition-Notification-To: response@localhost"-H "DISPOSITION-NOTIFICATION-OPTIONS: signed-receipt-protocol=required, pkcs7-signature; signed-receipt-micalg=optional, sha1"--data-binary @GETMSG_ENC.msg http://localhost:8080/pyas2/as2receive
    

    Note: this command would also request MDN with signature (because of presence of Disposition-Notification-To and value of DISPOSITION-NOTIFICATION-OPTIONS).

    Receiving Message

    The following will already happen on receiving side. That would include decrypting message, verifying signature, extracting payload and preparing acknowledgment or message disposition notification (MDN). It would also require to calculate hash of received message (MIC) to be send in acknowledgment.

    Decrypt Request

    Using OpenSSL command decode request content saved as smime.p7m:

    openssl smime -decrypt -in smime.p7m -recip P2_public.pem -inkey P2_private.pem -out request.txt
    

    Verify Signature

    openssl smime -verify -in request.txt -nosigs -noverify -signer P2_public.pem -out original.txt
    

    Calculate Received-content-MIC

    Digest of received content has to be calculated over original file WITH MIME headers, in our case that is content of GETMSG.msg.

    openssl dgst -sha1 -binary GETMSG.msg | openssl enc -e -base64
    

    Note: in real life receiver has to extract that from decrypted content of type multipart/signed. First part is the received content.

    Message Disposition Notification

    Not signed message disposition notification or acknowledgement would look like multipart report:

    Content-Type: multipart/report; report-type="disposition-notification"; boundary="===============1785295974=="
    
    --===============1785295974==
    Content-Type: text/plain; charset="us-ascii"
    Content-Transfer-Encoding: 7bit
    
    The AS2 message has been processed. Thank you for exchanging AS2 messages with Pyas2.
    
    --===============1785295974==
    Content-Type: message/disposition-notification; charset="us-ascii"
    Content-Transfer-Encoding: 7bit
    
    Reporting-UA: Bots Opensource EDI Translator
    Original-Recipient: rfc822; p1as2
    Final-Recipient: rfc822; p1as2
    Original-Message-ID: <openssl20170706165018@LOCALHOST>
    Disposition: automatic-action/MDN-sent-automatically; processed
    Received-content-MIC: 1GZ1SDk5vvGz5YFGYP6lfhk4MXE=, sha1
    
    --===============1785295974==--
    

    Signed Message Disposition Notification

    If signed MDN is requested (depends on 'Disposition-Notification-Options' HTTP header) then multipart report shown above is wrapped in multipart/signed. And seconds part is signature of the first part shown above. If we assume that mdn.txt contains not signed MDN as shown above, then to sign it:

    openssl cms -sign -signer P2_private.pem -in mdn.txt -out mdn_signed.txt
    

    Now we need only to add some AS2 specific headers to the top of mdn.txt or mdn_signed.txt, depending on what kind of MDN was requested, before transmitting it over HTTP (taken from actual response of pyAS2):

    ediint-features: CEM
    as2-from: p1as2
    user-agent: PYAS2, A pythonic AS2 server
    AS2-Version: 1.2
    as2-to: p2as2
    date: Thu, 06 Jul 2017 16:50:18 +0200
    X-Frame-Options: SAMEORIGIN
    Message-ID: <149935261885.25752.7388914440262498594@HOSTNAME>
    Transfer-Encoding: chunked
    Server: pyas2-webserver
    

    Based on sources: pyAS2 documentation, OpenSSL documentation, NCPDP SCRIPT message example.