Published on

Invio di PEC automatico con Java

Authors

Io odio le PEC. Le odio con tutto me stesso, davvero. Eppure sono l'unico modo per inviare raccomandate con ricevuta di consegna digitale in Italia (e solo in Italia, chissà perché). Non hanno senso, sono state progettate da persone che di programmazione non hanno mai visto neanche un Hello World, e sono davvero poco sicure, e fosse per me non le userei mai e poi mai.

Ma non sono qui per lamentarmi e fare un post chilometrico in cui spiego tutte le infinite ragioni per cui fanno pena, perché so che non cambierei nulla. Sono qui invece per informarvi su come inviare PEC programmaticamente tramite una "semplice" applicazione scritta in Java.

Ma prima un paio di domande importanti.

Ha effettivamente senso per il vostro business case?

In primis, metto le mani avanti: automatizzare l'invio di PEC non è una buona idea. Assolutamente. Io non lo farei. Eppure ci sono dei casi in cui può risultare utile. Lascio a voi decidere se implementare questo sistema vi serva davvero oppure no. Rifletteteci bene.

Ma non esistono già dei tutorial o esempi di codice online?

Dato che le PEC esistono solo in Italia, online non esistono tutorial o esempi quantomeno decenti su come automatizzare il loro invio (in realtà esistono, ma sono scritti malissimo e il solo guardarli mi fa riflettere su quale sia effettivamente la qualità media dei programmatori in Italia). Quindi eccomi qui.

Basta chiacchiere, show me the code!

Prima di tutto ci serve una classe che descriva bene il messaggio PEC che vogliamo inviare sotto ogni suo aspetto. E' abbastanza semplice:

public class MailModel {
        private String html; // Message body
        private String from; // Sender's email
        private String sender; // Sender's full name
        private String replyTo;
        private List<Recipient> recipients;
        private List<Attachment> attachments;

        public static class Recipient {
                private String email;
                private String name; // Full name
                private String subject;
        }

        public static class Attachment {
                private String url;
                private String fileName;
                private String description;
        }
}

Ho omesso tutti i getters, i setters e i costruttori per brevità (altrimenti la classe veniva davvero di millemila righe, una cosa che odio profondamente del Java, problema Kotlin risolve elegantemente (imparate Kotlin, vi prego!!!). Sono sicuro che voi siate ampiamente in grado di scriverveli da soli (altrimenti non dovreste essere qui, davvero, imparate almeno le basi del Java prima di continuare a programmare).

Il prossimo step è implementare in un opportuno MailService il metodo sendMailPEC che prende come parametro un singolo MailModel e si occuperà di effettuare l'invio di tutte le mail. Ma come funziona esattamente?

Semplice: tramite il provider della PEC (nel nostro caso sarà Aruba, bleah) utilizzeremo le credenziali di un account già esistente per inviare le mail come se le stessimo inviando direttamente dal client ufficiale di Aruba (ecco, questo è esattamente ciò che avviene dietro le quinte di Aruba quando inviamo una PEC).

Esatto: la password dell'account sarà una property della applicazione in chiaro. E' importante mantenerla segreta, quindi il mio consiglio è di non fare il checkout dei file di configurazione segreti sul sistema Git, oppure più semplicemente di lavorare su una repo privata tramite Bitbucket.

Inutile dire che se il provider non è Aruba dovrete cambiare i valori di pec.hostname e se necessario anche di pec.port.

Visto che io lavoro su Spring, utilizzerò il suo sistema di configurazione in questo modo:

pec.sender_mail = EMAIL_PEC
pec.hostname = smtps.pec.aruba.it
pec.port = 465
pec.auth.username = EMAIL_PEC
pec.auth.password = PASSWORD

Al posto di EMAIL_PEC scrivete la vostra mail PEC che volete usare per mandare i messaggi e al posto di PASSWORD, ovviamente la vostra password segreta. Senza delle credenziali, non è possibile connettersi al sistema di invio! (Aruba, perché diamine non hai implementato un sistema di API key revocabili? Tutto io vi devo dire...)

Ora passiamo al succo del codice, ossia l'implementazione di sendMailPEC in MailService. Taglio come al solito il codice in più che non serve:

import java.util.*;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.commons.mail.EmailAttachment;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Service("MailService")
@PropertySource(value = "classpath:application.properties")
public class MailServiceImpl implements MailService {
        private Logger logger = LoggerFactory.getLogger(MailServiceImpl.class);
        private static String logPattern = "[MAIL Service]";

        @Value("${pec.sender_mail}")
        @NotNull
        private String pecSenderMail;

        @Value("${pec.hostname}")
        @NotNull
        private String pecHostName;

        @Value("${pec.port}")
        @NotNull
        private int pecPort;

        @Value("${pec.auth.username}")
        @NotNull
        private String pecAuthUsername;

        @Value("${pec.auth.password}")
        @NotNull
        private String pecAuthPassword;

        @Override
        public void sendMailPEC(MailModel mailModel) {
                logger.info("{} - Sending PEC mail to {} recipients", logPattern, mailModel.getRecipients().size());

                // Prepare system properties
                Properties props = System.getProperties();
                props.setProperty("mail.smtp.ssl.enable", "true");
                props.setProperty("mail.smtp.ssl.socketFactory.class", CustomSSLSocketFactory.class.getName());
                props.setProperty("mail.smtp.ssl.socketFactory.fallback", "false");

                mailModel.getRecipients().forEach(recipient -> {
                        try {
                // Prepare email
                HtmlEmail email = new HtmlEmail();

                email.setHostName(pecHostName);
                email.setSmtpPort(pecPort);
                email.setAuthentication(pecAuthUsername, pecAuthPassword);
                email.setSSL(true);

                email.setFrom(pecSenderMail);
                email.setHtmlMsg(mailModel.getHtml());
                email.setSubject(recipient.getSubject());
                email.addTo(recipient.getEmail(), recipient.getName());

                for (MailAttachment attachment : Optional.ofNullable(model.getAttachments()).orElse(Collections.emptyList())) {
                    EmailAttachment emailAttachment = new EmailAttachment();
                    emailAttachment.setDisposition(EmailAttachment.ATTACHMENT);
                    emailAttachment.setURL(new URL(attachment.getURL()));
                    emailAttachment.setName(attachment.getFileName());
                    emailAttachment.setDescription(attachment.getDescription());
                    email.attach(emailAttachment);
                }

                // Finally send email to recipient
                email.send();
            } catch (EmailException | MalformedURLException e) {
                logger.error("{} - Failed to send PEC mail! ({} error)", logPattern, e.getClass().getName());
                logger.error("{} - Error message: {}. Caused by: {}", logPattern, e.getMessage(), e.getCause());
            }
                });

                logger.info("{} - Sent PEC mails to {} recipients", logPattern, mailModel.getRecipients().size());
        }
}

Il codice, in realtà, è molto semplice. Ciò che dovrebbe catturare la vostra attenzione sono queste righe:

// Prepare system properties
Properties props = System.getProperties();
props.setProperty("mail.smtp.ssl.enable", "true");
props.setProperty("mail.smtp.ssl.socketFactory.class", CustomSSLSocketFactory.class.getName());
props.setProperty("mail.smtp.ssl.socketFactory.fallback", "false");

Cosa significano? In realtà non vi serve saperlo nel dettaglio, sarei davvero noioso (e comunque potete leggervi la documentazione online di queste particolari classi), tutto ciò che vi serve sapere è che se omettete queste righe non funzionerà nulla, poichè la connessione tramite SMTPS sarà rifiutata a prescindere.

Essenzialmente (e sto semplificando molto) le factory di default sono impostate in maniera troppo "restrittiva" (sicura, ecco), e quindi non consentiranno al primo stronzo di accedere. Occorre fare un importante override di queste factory, con le nostre personali implementazioni con blackjack e squillo di lusso. Anzi, senza factory e senza blackjack.

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;

import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;

public class CustomSSLSocketFactory extends SSLSocketFactory {

    private SSLSocketFactory factory;

    private CustomSSLSocketFactory() {
        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, new TrustManager[] { new CustomTrustManager() }, null);
            factory = sc.getSocketFactory();
        } catch (Exception ex) {
            // Ignore any exception thrown
        }
    }

    public static SocketFactory getDefault() {
        return new CustomSSLSocketFactory();
    }

    public Socket createSocket() throws IOException {
        return factory.createSocket();
    }

    public Socket createSocket(Socket socket, String s, int i, boolean flag) throws IOException {
        return factory.createSocket(socket, s, i, flag);
    }

    public Socket createSocket(InetAddress inaddr, int i, InetAddress inaddr1, int j) throws IOException {
        return factory.createSocket(inaddr, i, inaddr1, j);
    }

    public Socket createSocket(InetAddress inaddr, int i) throws IOException {
        return factory.createSocket(inaddr, i);
    }

    public Socket createSocket(String s, int i, InetAddress inaddr, int j) throws IOException {
        return factory.createSocket(s, i, inaddr, j);
    }

    public Socket createSocket(String s, int i) throws IOException {
        return factory.createSocket(s, i);
    }

    public String[] getDefaultCipherSuites() {
        return factory.getDefaultCipherSuites();
    }

    public String[] getSupportedCipherSuites() {
        return factory.getSupportedCipherSuites();
    }

}

E infine, implementiamo CustomTrustManager

import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;

public class CustomTrustManager implements X509TrustManager {

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) {
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) {
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }

}

Essenzialmente queste nuove classi consentiranno praticamente qualunque connessione, rendendo anche possibili attacchi MITM poiché tutto il sistema dei certificati si è andato ufficialmente a farsi benedire. Quindi per favore se ne subite uno (spero vivamente di no) non prendetevela con me. Sapete a quali rischi andate incontro con questo sistema.

Di nuovo, se solo quel fottuto Aruba avesse un sistema di API decente (e non ce l'ha) tutto questo non sarebbe necessario. Basterebbe solo chiamare le loro API sicure con un token e via! Classiche italianate del cazzo... e io che ci vado pure appresso!

Tutto qui?

Sì, questo è quanto. Questo codice vi consentirà di mandare email PEC programmaticamente. Incredibile no? No non lo è. Questo codice apre una forte vulnerabilità nella vostra applicazione, e se non siete disposti a correre il rischio che qualcuno malauguratamente la sgami, non avete alcun altro modo di inviare PEC (sì, dico sul serio!). A meno che voi non vogliate spendere 2000 euro all'anno stando dietro a MailUp... la scelta è solo vostra...

Alla prossima!