as2

package module
v0.0.0-...-3894b01 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 7, 2026 License: MIT Imports: 30 Imported by: 0

README

WAARP AS2

A high-performance, compliant AS2 (Applicability Statement 2) protocol implementation in Go. This library provides both Client and Server capabilities for exchanging structured business data securely over HTTP/S, following RFC 4130.

Features

  • Full AS2 Version 1.1 Support
  • Security: S/MIME signing and encryption (AES-128/256, DES, 3DES).
  • Transport: HTTP and HTTPS support.
  • Reliability: Synchronous and Asynchronous MDN (Message Disposition Notification) support.
  • Compression: Support for compressed data (RFC 5402).
  • Flexibility: Interchangeable storage backends for Messages and Partners.
  • Modern Go: Built with log/slog for structured logging and standard net/http.

Installation

go get code.waarp.fr/lib/as2

Usage

1. AS2 Server

The server listens for incoming AS2 messages, handles decryption/verification, and automatically sends MDNs.

package main

import (
	"log/slog"
	"os"
	
	"code.waarp.fr/lib/as2"
)

func main() {
	// 1. Configure the Logger
	logger := slog.New(slog.NewTextHandler(os.Stdout, nil))

	// 2. Load Certificates (for signing MDNs and decrypting messages)
	mdnCert, mdnKey, _ := as2.LoadCertKey("mdn_cert.pem", "mdn_key.pem")
	decryptCert, decryptKey, _ := as2.LoadCertKey("decrypt_cert.pem", "decrypt_key.pem")

	// 3. Initialize Server with Options
	server := as2.NewServer(
		as2.WithLogger(logger),
		as2.WithLocalAS2ID("MY_COMPANY_AS2_ID"),
		as2.WithInboundDecryptIdentity(decryptCert, decryptKey), // Enable decryption
		as2.WithMDNSigner(mdnCert, mdnKey),                      // Enable signed MDNs
		// Optional: Define custom storage or file handling
		// as2.WithFileHandler(myCustomHandler),
	)

	// 4. Start Listening
	if err := server.ListenAndServe(":8080"); err != nil {
		logger.Error("Server failed", "error", err)
	}
}
2. AS2 Client

The client sends messages to remote AS2 partners and processes the returned MDN.

package main

import (
    "context"
    "code.waarp.fr/lib/as2"
)

func main() {
    client := as2.NewClient(as2.WithClientAS2ID("MY_COMPANY_AS2_ID"))
    
    // Define the partner configuration
    partner := &as2.Partner{
        AS2ID:     "PARTNER_AS2_ID",
        SendUrl:   "http://partner.example.com/as2",
        // Load partner's public certificate for encryption/verification
        CertChain: loadPartnerPublicCerts(), 
    }

    // Send a message
    data := []byte("ISA*00*...") // EDI payload
    
    opts := as2.NewSendOptions(
        as2.WithFileName("invoice_123.edi"),
        as2.WithContentType("application/edi-x12"),
        as2.WithRequestMDN(true),
        as2.WithSignedMDN(true), // Request Signed MDN
    )

    // Enable Signing and Encryption for this message
    opts.Sign = as2.DefaultSignPKCS7
    opts.Encrypt = as2.DefaultEncryptPKCS7

    result, err := client.Send(context.TODO(), partner, data, opts)
    if err != nil {
        panic(err)
    }

    if result.MDN != nil {
        println("MDN Received:", result.MDN.Disposition)
    }
}

API Reference

Client API
NewClient(opts ...ClientOption) *Client

Creates a new AS2 Client.

Option Description Default
WithClientAS2ID(id string) Sets the AS2-From header value. "WAARP_AS2_ID"
WithMDNAddress(addr string) Sets the default Disposition-Notification-To address. ""
WithSigner(priv, chain) Configures the client's private key for signing outgoing messages. nil
WithHTTPClient(h *http.Client) Use a custom http.Client. &http.Client{Timeout: 60s}
WithClientLogger(l *slog.Logger) Sets the logger for client operations. slog.DiscardHandler
WithUserAgent(ua string) Sets the User-Agent header. "WAARP-AS2/0.1 (client)"
Client.Send(ctx, partner, payload, opts) (*SendResult, error)

Sends a message to the specified Partner. Returns a SendResult containing the HTTP status and the parsed MDN (if synchronous).

Send Configuration
NewSendOptions(opts ...SendOption) *SendOptions

Helper to create the options struct with defaults. Alternatively, you can instantiate &SendOptions{} directly.

Option Description Default
WithFileName(name string) Sets the filename for Content-Disposition. ""
WithContentType(ct string) Sets the Content-Type. "application/octet-stream"
WithMessageID(id string) Overrides the auto-generated Message-ID. Auto-generated
WithSubject(subj string) Sets the Subject header. ""
WithRequestMDN(bool) Requests an MDN from the receiver. true
WithSignedMDN(bool) Requests the MDN be signed. true
WithAsyncReturnURL(url string) Requests an Asynchronous MDN sent to this URL. "" (Synchronous)
WithCompression(bool) Compresses the payload (ZLIB) before signing/encrypting. false
WithSignDigest(alg MICAlg) Sets the digest algorithm for signing. SHA-256
WithSignFunc(SignFunc) Sets the signing function. DefaultSignPKCS7
WithEncryptFunc(EncryptFunc) Sets the encryption function. DefaultEncryptPKCS7
WithExpectContinue(bool) Adds Expect: 100-continue header. false
WithTimeout(d time.Duration) Sets a specific timeout for this send operation. 0 (Client timeout)
Server API
NewServer(opts ...ServerOption) *Server

Creates a new AS2 Server.

Option Description Default
WithLocalAS2ID(id string) Sets the expected AS2-To header (and AS2-From in MDNs). "WAARP_AS2_ID"
WithInboundPath(path string) URL path to listen on. "/as2"
WithMDNPath(path string) URL path for Async MDN listener. "/as2/mdn"
WithInboundDecryptIdentity(cert, key) Enables decryption of incoming encrypted messages. nil (Encrypted msgs rejected)
WithMDNSigner(cert, key) Enables signing of outgoing MDNs. nil (MDNs unsigned)
WithPartnerStore(store) Sets the backend for looking up partners. InMemoryPartnerStore
WithMessageStore(store) Sets the backend for persisting messages/MDNs. InMemoryMessageStore
WithFileHandler(handler) Function called when a valid message is received. nil
WithLogger(l *slog.Logger) Sets server logger. slog.DiscardHandler
WithStrict(bool) Enforces strict AS2 header validation. true
WithPublicBaseURL(url string) Sets the public base URL of the server (used for generating MDN links). ""
WithServerUserAgent(ua string) Sets the Server header. "WAARP-AS2"
WithTimeouts(rh, r, w, i) Configures HTTP server timeouts. 10s/30s/60s/120s
WithMaxBodyBytes(limit int64) Sets the maximum allowed request body size. 100 MB
WithSMTP(config *SMTPConfig) Configures SMTP for sending asynchronous MDNs via email. nil
WithMDNSignDigest(alg MICAlg) Sets the digest algorithm for MDN signing. SHA-256
WithHTTPAuthFunc(fn) Sets a callback to validate/authenticate HTTP requests before processing. nil
WithMDNPath(path string) Configures the path for the Async MDN handler (optional integration). "/as2/mdn"
3. Handling Asynchronous MDNs

To receive asynchronous MDNs (when a partner sends receipts back to a different URL), the server provides a dedicated MDNHandler.

func main() {
    // ... initialize your main server ...

    // Create the MDN Handler
    mdnHandler := as2.NewMDNHandler(logger, messageStore, partnerStore)
    
    // Register it on your HTTP multiplexer
    http.Handle("/as2/mdn", mdnHandler)
    
    // OR: The main Server struct automatically handles this if you use Server.ListenAndServe.
    // However, if you are integrating into an existing HTTP stack:
    // mdnh := server.MDNHandler() 
    // mux.Handle("/as2/mdn", mdnh)
}
MDNHandler.ServeHTTP(w, r)

Implements http.Handler. It:

  1. Validates AS2-specific headers (e.g., AS2-Version).
  2. Parses the MDN payload (supporting multipart/report and multipart/signed).
  3. Verifies the MIC (Message Integrity Check) against the original message (retrieved via MessageStore).
  4. Logs the receipt status.

Note: For standard usage via as2.NewServer(...), this handler is automatically configured and mounted if you use the provided ListenAndServe methods. You only need to manually instantiate it if you are building a custom HTTP routing layer.

Server.ListenAndServe(addr string) error

Starts the HTTP server on the given address.

Server.ListenAndServeTLS(addr, certFile, keyFile string) error

Starts the HTTPS server on the given address using the provided certificate and key files.

Server.Serve(l net.Listener) error

Serves AS2 requests on the provided net.Listener.

Server.ServeTLS(l net.Listener, certFile, keyFile string) error

Serves HTTPS AS2 requests on the provided net.Listener.

Server.Shutdown(ctx context.Context) error

Gracefully shuts down the server, waiting for active connections to complete.

Request Processing Order

The server enforces checks in the following strict order for every incoming request:

  1. Payload Size Limit: The server reads the entire request body into memory. If the size exceeds WithMaxBodyBytes (default 100MB), the connection is closed or a 400 Bad Request is returned.
  2. Authentication: If configured, WithHTTPAuthFunc is executed. The function receives the request with the fully read body.
  3. Method & Basic Headers: Validates the HTTP Method (POST), Host header, and Content-Type.
  4. AS2 Headers: Validates the presence and format of AS2-From and AS2-To.
  5. Strict Checks: If WithStrict(true) is set (default), verifies that AS2-To matches the server's LocalAS2ID.
Types & Interfaces
Partner

Represents a trading partner.

type Partner struct {
    AS2ID     string              // Partner's AS2 Identifier
    SendUrl   string              // Partner's AS2 URL (for sending messages)
    CertChain []*x509.Certificate // Partner's Public Certificates (for encryption and verification)
}
MessageStore

Interface for message persistence (duplicate checking and MDN storage).

  • Record(ctx, msgID, mdn, ...)
  • GetMDN(ctx, msgID)
PartnerStore

Interface for partner lookup.

  • GetByAS2From(ctx, id)

Advanced Configuration

Custom Signing & Encryption (Client)

You can override the default PKCS#7 signing and encryption logic by providing your own functions via WithSignFunc and WithEncryptFunc. This is useful if you need to use a specific cryptographic library (e.g., OpenSSL CGO bindings) or hardware security modules (HSM).

Key Type Definitions
// SignFunc creates a multipart/signed entity.
// It must return the full Content-Type (including boundary/micalg), the signed body, and the micalg used.
type SignFunc func(
    ctx context.Context,
    entity []byte,              // The MIME entity to sign (headers + body)
    signerCert *x509.Certificate,
    signerKey any,              // Private key
    digest MICAlg,              // Requested digest algorithm (e.g., "sha256")
) (contentType string, signed []byte, micalg string, err error)

// EncryptFunc wraps the entity in an enveloped-data structure.
// It must return the Content-Type (application/pkcs7-mime), the encrypted bytes, and the algorithm used.
type EncryptFunc func(
    ctx context.Context,
    entity []byte,              // The full MIME entity to encrypt
    recipients []*x509.Certificate,
) (contentType string, encrypted []byte, algo string, err error)
Server Authentication

By default, the AS2 Server does not enforce any HTTP authentication (auth is done via AS2-To/AS2-From headers and optional signature verification).

To secure the HTTP transport layer (e.g., Basic Auth, Bearer Token), you can provide a custom HTTPAuthFunc. This function is executed after the body is read but before any AS2 processing.

func main() {
    as2.NewServer(
        as2.WithHTTPAuthFunc(func(r *http.Request) bool {
            // Example: Enforce Basic Auth
            user, pass, ok := r.BasicAuth()
            if !ok {
                return false
            }
            return user == "admin" && pass == "secret"
        }),
    )
}

Technical Choices & Architecture

Language & Runtime

Written in Go for its strong concurrency primitives (goroutines), type safety, and efficient net/http standard library. AS2 requires handling heavy I/O and cryptographic operations; Go is well-suited for high-throughput environments.

Cryptography (S/MIME)

We utilize github.com/smallstep/pkcs7 for low-level interaction with Cryptographic Message Syntax (CMS).

  • Why? The Go standard library crypto/x509 does not provide high-level S/MIME or PKCS#7 building blocks required for AS2 (EnvelopedData, SignedData).
  • Algorithms: We enforce strict algorithm normalization. While MD5 and SHA-1 are supported for backward compatibility with legacy AS2 systems, SHA-256 and AES-256 are preferred and default.
Storage Abstraction

The system is designed with Dependency Injection for storage:

  • PartnerStore: Interface to retrieve partner profiles (keys, certificates, URLs).
  • MessageStore: Interface to persist message logs and MDNs.
  • Default: In-memory implementations are provided for testing/lightweight usage, but production use should implement these interfaces with a database (SQL, Redis, etc.).
Logging

Uses Go's standard log/slog (introduced in Go 1.21).

  • Why? Provides structured, level-based logging with practically zero overhead when disabled. It allows easy integration with log aggregators (Elastic, Datadog) without enforcing a third-party dependency.

Protocol Implementation Status

This implementation is checked against RFC 4130.

Feature RFC 4130 Section Status Notes
Transport
HTTP POST 5.1 ✅ Implemented
HTTPS (TLS) 5.1/9.2 ✅ Implemented Native Go TLS support.
Security (S/MIME)
Signing (Multipart/Signed) 3.2 / 7.3 ✅ Implemented SHA-1, SHA-256, SHA-384, SHA-512.
Encryption (EnvelopedData) 2.4 / 3.7 ✅ Implemented AES-128/256 (-CBC/-GCM), DES, 3DES.
Signed & Encrypted 2.4.2 ✅ Implemented Nested S/MIME entities.
Compression RFC 5402 ✅ Implemented CompressedData support (zlib).
Reliability (MDN)
Synchronous MDN 7.2 ✅ Implemented Returned in HTTP Response.
Asynchronous MDN (HTTP) 7.2 ✅ Implemented Via Receipt-Delivery-Option.
Asynchronous MDN (SMTP) 7.2 ✅ Implemented Via Email/SMTP.
Signed MDN 7.3 ✅ Implemented PKCS#7 Detached Signature.
Unsigned MDN 7.4 ✅ Implemented Simple text status.
MIC Calculation 7.4 ✅ Implemented Validates integrity of received payload.
Headers
AS2-Version 6.1 ✅ Implemented Sends 1.1.
Message-ID 5.3.3 ✅ Implemented RFC 2822 compliant.
Algorithms
MD5 ⚠️ Legacy Supported but warns on use.
SHA-1 ⚠️ Legacy Supported but warns on use.
SHA-256+ ✅ Recommended Default for modern exchanges.

Documentation

Index

Constants

View Source
const MaxBodyDefault = 100 << 20 // 100 MiB

Variables

View Source
var (
	ErrMissingHost               = errors.New("missing Host header")
	ErrMissingContentType        = errors.New("missing Content-Type header")
	ErrMissingAS2Identity        = errors.New("missing AS2-From/AS2-To")
	ErrAS2IdentityTooLong        = errors.New("AS2-From/AS2-To too long")
	ErrInvalidAS2IdentityFolding = errors.New("AS2-From/AS2-To invalid folding")
	ErrInvalidAS2IdentityFormat  = errors.New("AS2-From/AS2-To must be printable ASCII")
	// ErrInvalidPartIndex is returned when a MIME part index is out of bounds.
	ErrInvalidPartIndex = errors.New("invalid part index")

	ErrAsyncMDNPostFailed = errors.New("async MDN POST failed")
	// ErrEmptyRDO is returned when Receipt-Delivery-Option is empty for async MDN.
	ErrEmptyRDO               = errors.New("empty Receipt-Delivery-Option URL")
	ErrUnsupportedRDO         = errors.New("unsupported Receipt-Delivery-Option scheme")
	ErrMDNSignerNotConfigured = errors.New("mdn signer not configured")
	ErrSMTPNotConfigured      = errors.New("smtp not configured")

	ErrSignatureVerificationFailed = errors.New("signature verification failed")
	ErrSignerCertMismatch          = errors.New("signer certificate mismatch")
	ErrDecryptIdentityMissing      = errors.New("missing decryption identity")
	ErrDecryptFailed               = errors.New("decrypt failed")
	ErrTrailingData                = errors.New("trailing data after ContentInfo")

	ErrSignMissingKeyOrCert      = errors.New("sign: missing signer key or cert chain")
	ErrEncryptPartnerCertMissing = errors.New("encrypt: partner certificate missing")
	ErrEmptyContentType          = errors.New("empty content type")

	ErrServerStartFailed    = errors.New("failed to start AS2 server")
	ErrServerShutdownFailed = errors.New("failed to shutdown AS2 server")
	ErrAsyncMDNRequestBuild = errors.New("failed to build async MDN request")
	ErrAsyncMDNRequestSend  = errors.New("failed to send async MDN request")

	ErrNotMDNMultipartReport = errors.New("not an MDN multipart/report")
	ErrMDNMissingDisposition = errors.New("MDN missing Disposition")
	ErrUnsupportedMICAlg     = errors.New("unsupported MIC algo")
	ErrEmptyCertChain        = errors.New("empty certificate chain")
	ErrSignedMDNExpected     = errors.New("signed MDN requested but unsigned MDN received")
	ErrMDNMessageIDMismatch  = errors.New("MDN Original-Message-ID mismatch")
	ErrMDNAS2HeaderMismatch  = errors.New("MDN AS2 headers mismatch")
	ErrMICMismatch           = errors.New("MDN MIC mismatch")
	ErrMissingMIC            = errors.New("MDN missing Received-content-MIC")

	ErrNotCompressedData       = errors.New("not CompressedData")
	ErrUnsupportedCompression  = errors.New("unsupported compression algorithm")
	ErrUnsupportedMDNMediaType = errors.New("unsupported MDN media type")
	ErrMissingBoundary         = errors.New("multipart/signed missing boundary parameter")
	ErrUnsupportedPKCS7Payload = errors.New("application/pkcs7-mime payloads are not supported")
	ErrUnsupportedCTE          = errors.New("unsupported Content-Transfer-Encoding")

	ErrEmptyPayloadMIC        = errors.New("empty payload for MIC")
	ErrMissingReportPart      = errors.New("signed MDN missing report part")
	ErrMissingSignaturePart   = errors.New("signed MDN missing signature part")
	ErrInvalidDispositionAddr = errors.New("invalid Disposition-Notification-To address")
	ErrMissingDispositionAddr = errors.New("Disposition-Notification-To is required but no valid email address provided")
	ErrReceivedMICNoAlgo      = errors.New("MDN Received-content-MIC has no algorithm")
	ErrSMTPFromEmpty          = errors.New("smtp from address is empty")
	ErrSMTPHostEmpty          = errors.New("smtp host is empty")
	ErrEmptyBoundary          = errors.New("empty boundary")

	ErrNilPartner                 = errors.New("nil Partner")
	ErrMissingIDOrURL             = errors.New("missing AS2ID or SendUrl")
	ErrMissingPayloadCContentType = errors.New("missing payload Content-Type")
	ErrBuildingMIME               = errors.New("build MIME failed")
	ErrSigningPayload             = errors.New("sign payload failed")
	ErrCompressingPayload         = errors.New("compress payload failed")
	ErrEncryptingPayload          = errors.New("encrypt payload failed")
	ErrCreatingRequest            = errors.New("create request failed")
	ErrGeneratingHeader           = errors.New("generate AS2 header failed")
	ErrSendingRequest             = errors.New("send request failed")
	ErrParsingMDN                 = errors.New("parse MDN failed")
	ErrCanonicalizingMDN          = errors.New("canonicalize MDN failed")
	ErrDecodingSignature          = errors.New("decode signature failed")
	ErrSignedMDNMissingBoundary   = errors.New("signed MDN missing boundary")
	ErrSignedMDNParsePart         = errors.New("signed MDN parse part failed")
	ErrSignedMDNReadPart          = errors.New("signed MDN read part failed")

	ErrBoundaryNotFound  = errors.New("boundary not found")
	ErrMalformedBoundary = errors.New("malformed boundary")
	// ErrMessageNotFound is returned when a message is not found in the store.
	ErrMessageNotFound         = errors.New("message not found")
	ErrPartNotFound            = errors.New("part not found")
	ErrClosingBoundaryNotFound = errors.New("closing boundary not found")
	ErrCertNil                 = errors.New("certificate is nil")
	ErrCertNotYetValid         = errors.New("certificate not yet valid")
	ErrCertExpired             = errors.New("certificate expired")
	ErrCertRevoked             = errors.New("certificate is revoked")
)
View Source
var (
	ErrIMPSAS2IDEmpty      = errors.New("partner or AS2ID is empty")
	ErrIMPSPartnerNotFound = errors.New("partner not found")
)
View Source
var (

	// DefaultPKCS7EncryptionAlgorithm is the default algorithm used by
	// DefaultEncryptPKCS7.
	DefaultPKCS7EncryptionAlgorithm = PKCS7EncAES256CBC
)

Global protection around pkcs7.ContentEncryptionAlgorithm.

Idea:

  • RLock during Encrypt => multiple goroutines can encrypt simultaneously with the same algo.
  • Lock only when modifying the global algo.
View Source
var ErrDuplicateMessage = errors.New("duplicate message")

Functions

func DefaultEncryptPKCS7

func DefaultEncryptPKCS7(
	_ context.Context,
	entity []byte,
	recipients []*x509.Certificate,
) (string, []byte, string, error)

func DefaultSignPKCS7

func DefaultSignPKCS7(
	_ context.Context,
	entity []byte,
	cert *x509.Certificate,
	key any,
	digest MICAlg,
) (string, []byte, string, error)

func NewAS2PostHandler

func NewAS2PostHandler(opts *PostHandlerOpts) http.HandlerFunc

func SetPKCS7ContentEncryptionAlgorithm

func SetPKCS7ContentEncryptionAlgorithm(algo PKCS7EncryptionAlgorithm)

SetPKCS7ContentEncryptionAlgorithm change the global algorithm used by pkcs7.Encrypt in a thread-safe manner.

Types

type CertificateValidator

type CertificateValidator interface {
	// Validate checks if the certificate is valid (not expired, not revoked).
	// issuer is optional (can be nil if unknown).
	Validate(cert, issuer *x509.Certificate) error
}

CertificateValidator defines the interface for validating certificates.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is the AS2 client.

func NewClient

func NewClient(opts ...ClientOption) *Client

NewClient creates a new AS2 client.

func (*Client) Send

func (c *Client) Send(ctx context.Context, partner *Partner, payload []byte, opts *SendOptions) (*SendResult, error)

type ClientOption

type ClientOption func(*Client)

ClientOption is a function that configures the client.

func WithClientLogger

func WithClientLogger(l *slog.Logger) ClientOption

WithClientLogger sets the logger.

func WithHTTPClient

func WithHTTPClient(h *http.Client) ClientOption

WithHTTPClient overrides the HTTP client.

func WithLocalClientAS2ID

func WithLocalClientAS2ID(id string) ClientOption

WithLocalClientAS2ID sets the local AS2 ID.

func WithMDNAddress

func WithMDNAddress(addr string) ClientOption

WithMDNAddress sets the default Disposition-Notification-To address (e.g. mailto:[email protected]).

func WithSigner

func WithSigner(priv any, chain []*x509.Certificate) ClientOption

WithSigner configures the private key and certificate chain for signing.

func WithUserAgent

func WithUserAgent(ua string) ClientOption

WithUserAgent sets the User-Agent for the client.

type DefaultValidator

type DefaultValidator struct {
	// CRLs is a map of Issuer DN -> CRL (parsed).
	// In a real implementation, this would likely be a dynamic store or cache.
	// For now, we provide a hook to check against a list of CRLs.
	CRLs []*x509.RevocationList
}

DefaultValidator implements basic validation (expiration). It can be extended to check CRLs.

func NewDefaultValidator

func NewDefaultValidator() *DefaultValidator

NewDefaultValidator creates a new validator.

func (*DefaultValidator) Validate

func (v *DefaultValidator) Validate(cert, _ *x509.Certificate) error

Validate checks expiration and revocation (if CRLs are provided).

type EncryptFunc

type EncryptFunc func(
	ctx context.Context,
	entity []byte,
	recipients []*x509.Certificate,
) (contentType string, encrypted []byte, algo string, err error)

EncryptFunc wraps the complete S/MIME entity and returns :

  • contentType (application/pkcs7-mime; smime-type=enveloped-data)
  • bytes of the enveloped-data entity

the entity param is the full MIME entity (signed or not) to encrypt (headers + body).

func NewPKCS7Encrypter

func NewPKCS7Encrypter(algo PKCS7EncryptionAlgorithm) EncryptFunc

NewPKCS7Encrypter creates an EncryptFunc that uses the specified algorithm. It safely swaps the global algorithm during encryption to ensure the desired algorithm is used, even if other goroutines are encrypting with different algorithms.

type FileHandler

type FileHandler func(ctx context.Context, filename string, payload []byte) error

FileHandler is a function that processes an incoming file.

type HTTPAuthFunc

type HTTPAuthFunc func(r *http.Request) bool

HTTPAuthFunc validates an incoming HTTP request before AS2 processing begins.

type InMemoryMessageStore

type InMemoryMessageStore struct {
	// contains filtered or unexported fields
}

func NewInMemoryMessageStore

func NewInMemoryMessageStore() *InMemoryMessageStore

func (*InMemoryMessageStore) GetMDN

func (s *InMemoryMessageStore) GetMDN(_ context.Context, msgID string) (*MDN, string, []byte, error)

func (*InMemoryMessageStore) Record

func (s *InMemoryMessageStore) Record(
	_ context.Context,
	msgID string,
	mdn *MDN,
	contentType string,
	rawResponse []byte,
) error

type InMemoryPartnerStore

type InMemoryPartnerStore struct {
	// contains filtered or unexported fields
}

InMemoryPartnerStore is an in-memory implementation of PartnerStore. The data is stored in memory and will be lost when the application exits.

It is not suitable for production use, but can be useful for testing or prototyping.

func NewInMemoryPartnerStore

func NewInMemoryPartnerStore() *InMemoryPartnerStore

NewInMemoryPartnerStore creates a new instance of InMemoryPartnerStore.

func (*InMemoryPartnerStore) Delete

func (s *InMemoryPartnerStore) Delete(_ context.Context, as2From string) error

func (*InMemoryPartnerStore) GetByAS2From

func (s *InMemoryPartnerStore) GetByAS2From(_ context.Context, as2From string) (*Partner, error)

func (*InMemoryPartnerStore) List

func (*InMemoryPartnerStore) Put

type MDN

type MDN struct {
	OriginalMessageID string
	Disposition       string
	ReceivedMIC       string // raw value from MDN (algo + base64 digest)
	HumanText         string
	Signed            bool   // always false in this skeleton
	Raw               []byte // original body received
}

MDN captures the parsed disposition notification (unsigned in this skeleton).

func (*MDN) VerifyMIC

func (m *MDN) VerifyMIC(expectedMIC string) error

VerifyMIC verifies that the MIC contained in the MDN matches the expected MIC calculated from the original message payload.

expectedMIC should be in the format "<digest-base64>, <algorithm>". Supported algorithms are sha1, md5, sha256, sha384, sha512.

type MDNHandler

type MDNHandler struct {
	// contains filtered or unexported fields
}

MDNHandler handles incoming asynchronous MDNs.

func NewMDNHandler

func NewMDNHandler(logger *slog.Logger, store MessageStore, partners PartnerStore) *MDNHandler

NewMDNHandler creates a new handler for incoming Async MDNs.

func (*MDNHandler) ServeHTTP

func (h *MDNHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements the http.Handler interface. It receives POST requests containing AS2 MDNs (Message Disposition Notifications), parses them, validates any signatures, and logs the receipt.

type MDNMode

type MDNMode int
const (
	MDNModeSync MDNMode = iota
	MDNModeAsyncPreferred
)

type MICAlg

type MICAlg string

MICAlg represents the digest algorithm used for the MIC / micalg parameter.

const (
	MICSHA1      MICAlg = "sha1"  // default RFC token
	MICSHA1Cap   MICAlg = "sha-1" // alternate spelling
	MICMD5       MICAlg = "md5"
	MICSHA256    MICAlg = "sha256"
	MICSHA256Cap MICAlg = "sha-256" // alternate spelling

	MICSHA384    MICAlg = "sha384"
	MICSHA384Cap MICAlg = "sha-384"
	MICSHA512    MICAlg = "sha512"
	MICSHA512Cap MICAlg = "sha-512"
)

type MessageStore

type MessageStore interface {
	// Record attempts to record a new message ID.
	// If the message ID already exists, it returns ErrDuplicateMessage.
	// It should also store the generated MDN and its raw bytes (body + Content-Type)
	// for future retrieval.
	Record(ctx context.Context, msgID string, mdn *MDN, contentType string, rawBody []byte) error

	// GetMDN retrieves the MDN associated with a previously processed message ID.
	// Returns nil if not found.
	GetMDN(ctx context.Context, msgID string) (*MDN, string, []byte, error)
}

MessageStore defines the interface for storing and retrieving message states to support duplicate detection (RFC 4130 Section 5.5).

type PKCS7EncryptionAlgorithm

type PKCS7EncryptionAlgorithm int

PKCS7EncryptionAlgorithm is a thin wrapper over the pkcs7 package's encryption algorithm enum.

type Partner

type Partner struct {
	// Identity/mapping (exchanged out-of-band)
	Name    string // display
	AS2ID   string // AS2-From or AS2-To (depends on local/remote)
	SendUrl string // sender's URL (for async MDN)

	// security & preferences (negotiated out-of-band)
	CertChain        []*x509.Certificate // public key cert chain
	RequireSignedMDN bool
	MDNMode          MDNMode
	MICAlgs          []string // ex: []string{"sha1"} (ordered by preference)
	Validator        CertificateValidator

	// Housekeeping
	CreatedAt time.Time
	UpdatedAt time.Time
}

type PartnerStore

type PartnerStore interface {
	Put(ctx context.Context, p *Partner) error
	GetByAS2From(ctx context.Context, as2From string) (*Partner, error)
	List(ctx context.Context) ([]*Partner, error)
	Delete(ctx context.Context, as2From string) error
}

type PostHandlerOpts

type PostHandlerOpts struct {
	Store        PartnerStore
	MessageStore MessageStore
	LocalAS2ID   string
	Logger       *slog.Logger
	Strict       bool
	UserAgent    string
	SMTPConfig   *SMTPConfig
	FileHandler  FileHandler
	HTTPAuthFunc HTTPAuthFunc

	DecryptCert *x509.Certificate
	DecryptKey  any
	// contains filtered or unexported fields
}

type SMTPConfig

type SMTPConfig struct {
	Host     string
	Port     int
	Username string
	Password string
	From     string
}

SMTPConfig holds the minimal settings required to send an email via the Go standard library SMTP client.

type SendOption

type SendOption func(*SendOptions)

SendOption is a functional option applied to SendOptions.

func WithAsyncReturnURL

func WithAsyncReturnURL(url string) SendOption

WithAsyncReturnURL sets Receipt-Delivery-Option for async MDN.

func WithCompression

func WithCompression(v bool) SendOption

WithCompression enables ZLIB compression of the payload.

func WithContentType

func WithContentType(ct string) SendOption

WithContentType sets the payload Content-Type (e.g. application/edi-x12).

func WithEncryptFunc

func WithEncryptFunc(f EncryptFunc) SendOption

WithEncryptFunc sets a custom encryption callback (S/MIME enveloped). If nil, the default encrypter (DefaultEncryptPKCS7) will be used when EncryptPayload is true.

func WithExpectContinue

func WithExpectContinue(v bool) SendOption

WithExpectContinue toggles the Expect: 100-continue header.

func WithFileName

func WithFileName(name string) SendOption

WithFileName sets the filename used in Content-Disposition of the payload part.

func WithMDNAddressOverride

func WithMDNAddressOverride(addr string) SendOption

WithMDNAddressOverride overrides the Disposition-Notification-To address for this message.

func WithMICAlg

func WithMICAlg(alg string) SendOption

WithMICAlg sets the MIC algorithm name used for the sender-side MIC string (normalized form like "sha-256", "sha1", "md5").

func WithMessageID

func WithMessageID(id string) SendOption

WithMessageID sets a custom Message-ID (if empty, it will be generated).

func WithRequestMDN

func WithRequestMDN(v bool) SendOption

WithRequestMDN toggles whether the sender requests an MDN.

func WithSign

func WithSign(f SignFunc, d MICAlg) SendOption

func WithSignedMDN

func WithSignedMDN(v bool) SendOption

WithSignedMDN toggles "want signed MDN" (Disposition-Notification-Options).

func WithSubject

func WithSubject(subj string) SendOption

WithSubject sets the AS2 Subject header.

func WithTimeout

func WithTimeout(d time.Duration) SendOption

WithTimeout overrides the per-request timeout for this send.

type SendOptions

type SendOptions struct {
	// contains filtered or unexported fields
}

SendOptions describe how we send a single AS2 message.

func NewSendOptions

func NewSendOptions(opts ...SendOption) *SendOptions

NewSendOptions creates a SendOptions with sensible defaults, then applies any provided functional options.

type SendResult

type SendResult struct {
	StatusCode int
	Headers    http.Header
	// Sync MDN only (when receiver answers inline). For async, this is nil.
	MDN *MDN
}

SendResult summarizes the HTTP exchange + MDN (sync path).

type Server

type Server struct {
	Store        PartnerStore
	MessageStore MessageStore
	// contains filtered or unexported fields
}

func NewServer

func NewServer(opts ...ServerOption) *Server

func (*Server) ListenAndServe

func (s *Server) ListenAndServe(addr string) error

func (*Server) ListenAndServeTLS

func (s *Server) ListenAndServeTLS(addr, certFile, keyFile string) error

func (*Server) Serve

func (s *Server) Serve(lc net.Listener) error

func (*Server) ServeTLS

func (s *Server) ServeTLS(lc net.Listener, certFile, keyFile string) error

func (*Server) Shutdown

func (s *Server) Shutdown(ctx context.Context) error

type ServerOption

type ServerOption func(*Server)

func WithDecryptCertKey

func WithDecryptCertKey(cert *x509.Certificate, key any) ServerOption

WithDecryptCertKey configures the certificate/key used to decrypt inbound enveloped-data (application/pkcs7-mime; smime-type=enveloped-data) messages. If unset, encrypted inbound payloads are rejected.

func WithFileHandler

func WithFileHandler(fh FileHandler) ServerOption

func WithHTTPAuthFunc

func WithHTTPAuthFunc(fn HTTPAuthFunc) ServerOption

func WithInboundDecryptIdentity

func WithInboundDecryptIdentity(cert *x509.Certificate, key any) ServerOption

func WithInboundPath

func WithInboundPath(p string) ServerOption

func WithLocalAS2ID

func WithLocalAS2ID(id string) ServerOption

func WithLogger

func WithLogger(l *slog.Logger) ServerOption

func WithMDNPath

func WithMDNPath(p string) ServerOption

func WithMDNSignDigest

func WithMDNSignDigest(d MICAlg) ServerOption

func WithMDNSignFunc

func WithMDNSignFunc(f SignFunc) ServerOption

func WithMDNSigner

func WithMDNSigner(cert *x509.Certificate, key any) ServerOption

func WithMaxBodyBytes

func WithMaxBodyBytes(limit int64) ServerOption

func WithMessageStore

func WithMessageStore(ms MessageStore) ServerOption

func WithPartnerStore

func WithPartnerStore(st PartnerStore) ServerOption

func WithPublicBaseURL

func WithPublicBaseURL(u string) ServerOption

func WithSMTP

func WithSMTP(cfg *SMTPConfig) ServerOption

func WithServerUserAgent

func WithServerUserAgent(ua string) ServerOption

func WithStrict

func WithStrict(b bool) ServerOption

func WithTimeouts

func WithTimeouts(rh, r, w, i time.Duration) ServerOption

type SignFunc

type SignFunc func(
	ctx context.Context,
	entity []byte,
	signerCert *x509.Certificate,
	signerKey any,
	digest MICAlg,
) (contentType string, signed []byte, micalg string, err error)

SignFunc create a multipart/signed (S/MIME detached signature) and returns:

  • full contentType (with protocol, micalg)
  • bytes of the multipart/signed entity

The entity param is the original MIME entity to sign (headers + body).

Source Files

  • client.go
  • client_model.go
  • compression.go
  • crypto.go
  • errors.go
  • handler.go
  • handler_as2.go
  • handler_duplicate.go
  • handler_flow.go
  • handler_http.go
  • handler_mdn.go
  • handler_parse.go
  • handler_parse_helpers.go
  • header.go
  • in_memory_message_store.go
  • in_memory_parter_store.go
  • log.go
  • mdn.go
  • message_store.go
  • mime_helpers.go
  • partner.go
  • partner_store.go
  • server.go
  • smtp.go
  • utils.go
  • validator.go

Directories

Path Synopsis
cmd
client command
example_mic command
server command
test

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL