Java tutorial
/* * Copyright (c) 2008-2012, Martijn Brinkers, Djigzo. * * This file is part of Djigzo email encryption. * * Djigzo is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License * version 3, 19 November 2007 as published by the Free Software * Foundation. * * Djigzo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public * License along with Djigzo. If not, see <http://www.gnu.org/licenses/> * * Additional permission under GNU AGPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, * wsdl4j-1.6.1.jar (or modified versions of these libraries), * containing parts covered by the terms of Eclipse Public License, * tyrex license, freemarker license, dom4j license, mx4j license, * Spice Software License, Common Development and Distribution License * (CDDL), Common Public License (CPL) the licensors of this Program grant * you additional permission to convey the resulting work. */ package mitm.application.djigzo.james.mailets; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.security.DigestOutputStream; import java.security.KeyStoreException; import java.security.MessageDigest; import java.security.cert.CertStoreException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.mail.MessagingException; import javax.mail.internet.AddressException; import javax.mail.internet.MimeMessage; import mitm.application.djigzo.EncryptionRecipientSelector; import mitm.application.djigzo.User; import mitm.application.djigzo.james.DjigzoMailAttributes; import mitm.application.djigzo.james.DjigzoMailAttributesImpl; import mitm.application.djigzo.james.MailAddressUtils; import mitm.application.djigzo.james.MailAttributesUtils; import mitm.application.djigzo.james.MailetUtils; import mitm.application.djigzo.james.SecurityInfoTags; import mitm.application.djigzo.service.SystemServices; import mitm.application.djigzo.workflow.KeyAndCertificateWorkflow; import mitm.application.djigzo.workflow.UserWorkflow; import mitm.application.djigzo.workflow.UserWorkflow.GetUserMode; import mitm.common.hibernate.DatabaseAction; import mitm.common.hibernate.DatabaseActionExecutor; import mitm.common.hibernate.DatabaseActionExecutorBuilder; import mitm.common.hibernate.DatabaseException; import mitm.common.hibernate.SessionManager; import mitm.common.mail.EmailAddressUtils; import mitm.common.mail.MailSession; import mitm.common.mail.MimeMessageWithID; import mitm.common.mail.SkipHeadersOutputStream; import mitm.common.properties.HierarchicalPropertiesException; import mitm.common.security.KeyAndCertStore; import mitm.common.security.KeyAndCertificate; import mitm.common.security.PKISecurityServices; import mitm.common.security.SecurityFactoryFactory; import mitm.common.security.StaticKeysPKISecurityServices; import mitm.common.security.certstore.X509CertStoreEntry; import mitm.common.security.smime.SMIMEInspector; import mitm.common.security.smime.handler.CertificateCollectionEvent; import mitm.common.security.smime.handler.RecursiveSMIMEHandler; import mitm.common.security.smime.handler.SMIMEHandlerException; import mitm.common.security.smime.handler.SMIMEInfoHandlerImpl; import mitm.common.util.CollectionUtils; import mitm.common.util.HexUtils; import mitm.common.util.ReadableOutputStreamBuffer; import mitm.common.util.SizeUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.NullOutputStream; import org.apache.commons.io.output.TeeOutputStream; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.text.StrBuilder; import org.apache.james.core.MailImpl; import org.apache.mailet.Mail; import org.apache.mailet.MailAddress; import org.hibernate.Session; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Mailet that tries to decrypt an encrypted S/MIME message, gets all certificates from the S/MIME message and adds security * info to the headers. The S/MIME handler can handle 'normal' message with attached S/MIME messages. * * @author Martijn Brinkers * */ public class SMIMEHandler extends AbstractDjigzoMailet { private final static Logger logger = LoggerFactory.getLogger(SMIMEHandler.class); /* * The number of times a database action should be retried when a ConstraintViolation occurs */ private final static int ACTION_RETRIES = 3; /* * The mailet initialization parameters used by this mailet. */ private enum Parameter { REMOVE_SIGNATURE("removeSignature"), IMPORT_CERTIFICATES("importCertificates"), ADD_INFO( "addInfo"), DECRYPT("decrypt"), DECOMPRESS("decompress"), MAX_RECURSION( "maxRecursion"), PROTECTED_HEADER("protectedHeader"), RETAIN_MESSAGE_ID( "retainMessageID"), STRICT("strict"), THRESHOLD("threshold"), SUBJECT_TEMPLATE( "subjectTemplate"), HANDLED_PROCESSOR("handledProcessor"), STRICT_ATTRIBUTE( "strictAttribute"), REMOVE_SIGNATURE_ATTRIBUTE( "removeSignatureAttribute"); private String name; private Parameter(String name) { this.name = name; } }; private class CertificateCollectionEventImpl implements CertificateCollectionEvent { @Override public void certificatesEvent(Collection<? extends X509Certificate> certificates) throws CertificateException { SMIMEHandler.this.certificatesEvent(certificates); } } /* * Is called when a certificate is extracted from the message */ private final CertificateCollectionEvent certificateCollectionEvent = new CertificateCollectionEventImpl(); /* * The maximum search depth for embedded S/MIME attachments. */ private int maxRecursion; /* * If true and the message is signed the signature will be removed. */ private boolean removeSignature; /* * If true all certificates attached to signed message will be imported into the certificate store */ private boolean importCertificates; /* * If true security info will be added to the message headers. */ private boolean addInfo; /* * If true and the message is encrypted the message will be decrypted */ private boolean decrypt; /* * If true and the message is compressed the message will be decompressed */ private boolean decompress; /* * If true, the original Message-ID will be used for the handled message */ private boolean retainMessageID; /* * If true, the message will only be decrypted for a recipient if the recipient * has the correct provate key. In non-strict mode, if the message can be decrypted, * the message will be delivered undecrypted to all recipients. */ private boolean strict; /* * The next processor for messages that were handled */ private String handledProcessor; /* * Determines which headers will be encrypted and/or signed */ private String[] protectedHeaders = new String[] {}; /* * Provides PKI services (root store, path builder etc.) */ private PKISecurityServices pKISecurityServices; /* * Is used for importing extracted certificates */ private KeyAndCertificateWorkflow keyAndCertificateWorkflow; /* * For getting users */ private UserWorkflow userWorkflow; /* * Used for the selection of encryption certificates for particular user(s). */ private EncryptionRecipientSelector encryptionRecipientSelector; /* * For managing database sessions */ private SessionManager sessionManager; /* * Is used to execute a method within a database transaction */ private DatabaseActionExecutor actionExecutor; /* * If exceeded, messages are stored in a temp file and not in memory */ private int threshold; /* * The subject template. The first parameter is replaced with the current subject and the second * parameter is replaced with the encryption or signing tag. * * Example subject: * * %1$s %2$s */ private String subjectTemplate; /* * The name of the strict Mail Attribute */ private String strictAttribute; /* * The name of the removeSignature Mail Attribute */ private String removeSignatureAttribute; @Override protected Logger getLogger() { return logger; } public int getThreshold() { return threshold; } public void setThreshold(int threshold) { this.threshold = threshold; } private void initProtectedHeaders() { String param = getInitParameter(Parameter.PROTECTED_HEADER.name); if (param != null) { protectedHeaders = param.split("\\s*,\\s*"); } } @Override final public void initMailet() throws MessagingException { getLogger().info("Initializing mailet: " + getMailetName()); removeSignature = getBooleanInitParameter(Parameter.REMOVE_SIGNATURE.name, false /* default */); importCertificates = getBooleanInitParameter(Parameter.IMPORT_CERTIFICATES.name, true/* default */); addInfo = getBooleanInitParameter(Parameter.ADD_INFO.name, true /* default */); decrypt = getBooleanInitParameter(Parameter.DECRYPT.name, true /* default */); decompress = getBooleanInitParameter(Parameter.DECOMPRESS.name, true /* default */); retainMessageID = getBooleanInitParameter(Parameter.RETAIN_MESSAGE_ID.name, true /* default */); strict = getBooleanInitParameter(Parameter.STRICT.name, false /* default */); maxRecursion = getIntegerInitParameter(Parameter.MAX_RECURSION.name, 32 /* default */); threshold = getIntegerInitParameter(Parameter.THRESHOLD.name, SizeUtils.MB * 5 /* default */); subjectTemplate = getInitParameter(Parameter.SUBJECT_TEMPLATE.name, "%1$s %2$s" /* default */); handledProcessor = getInitParameter(Parameter.HANDLED_PROCESSOR.name); if (handledProcessor == null) { throw new MessagingException("handledProcessor is missing."); } strictAttribute = getInitParameter(Parameter.STRICT_ATTRIBUTE.name); removeSignatureAttribute = getInitParameter(Parameter.REMOVE_SIGNATURE_ATTRIBUTE.name); initProtectedHeaders(); StrBuilder sb = new StrBuilder(); sb.append("Remove signature: ").append(removeSignature); sb.appendSeparator("; "); sb.append("Add info: ").append(addInfo); sb.appendSeparator("; "); sb.append("Decrypt: ").append(decrypt); sb.appendSeparator("; "); sb.append("Decompress: ").append(decompress); sb.appendSeparator("; "); sb.append("Retain Message-ID: ").append(retainMessageID); sb.appendSeparator("; "); sb.append("Strict: ").append(strict); sb.appendSeparator("; "); sb.append("handledProcessor: ").append(handledProcessor); sb.appendSeparator("; "); sb.append("Protected headers: "); sb.append(StringUtils.join(protectedHeaders, ",")); sb.appendSeparator("; "); sb.append("Max recursion: ").append(maxRecursion); sb.appendSeparator("; "); sb.append("threshold: ").append(threshold); sb.appendSeparator("; "); sb.append("subjectTemplate: ").append(subjectTemplate); sb.appendSeparator("; "); sb.append("strictAttribute: ").append(strictAttribute); sb.appendSeparator("; "); sb.append("removeSignatureAttribute: ").append(removeSignatureAttribute); getLogger().info(sb.toString()); sessionManager = SystemServices.getSessionManager(); actionExecutor = DatabaseActionExecutorBuilder.createDatabaseActionExecutor(sessionManager); assert (actionExecutor != null); pKISecurityServices = SystemServices.getPKISecurityServices(); keyAndCertificateWorkflow = SystemServices.getKeyAndCertificateWorkflow(); userWorkflow = SystemServices.getUserWorkflow(); encryptionRecipientSelector = SystemServices.getEncryptionRecipientSelector(); } private boolean isStrict(Mail mail) { return MailAttributesUtils.getBoolean(mail, strictAttribute, this.strict); } private boolean isRemoveSignatureAttribute(Mail mail) { return MailAttributesUtils.getBoolean(mail, removeSignatureAttribute, this.removeSignature); } private void sendNewMessage(Mail sourceMail, Collection<MailAddress> recipients, MimeMessage message, String processor) throws MessagingException { /* * We need to create a new Mail object this way to make sure that all attributes are cloned. */ MailImpl newMail = new MailImpl(sourceMail, MailetUtils.createUniqueMailName()); try { newMail.setRecipients(recipients); newMail.setMessage(message); newMail.setState(processor); getMailetContext().sendMail(newMail); } finally { newMail.dispose(); } } @Override public void serviceMail(final Mail mail) { try { final MimeMessage sourceMessage = mail.getMessage(); if (sourceMessage != null) { Messages messages = actionExecutor.executeTransaction(new DatabaseAction<Messages>() { @Override public Messages doAction(Session session) throws DatabaseException { Session previousSession = sessionManager.getSession(); sessionManager.setSession(session); try { return handleMessageTransacted(mail); } finally { sessionManager.setSession(previousSession); } } }, ACTION_RETRIES /* retry on a ConstraintViolationException */); if (messages != null) { try { Collection<MimeSource> mimeSources = messages.getMimeSources(); /* * Send all handled S/MIME message to the handledProcessor next processor */ for (MimeSource mimeSource : mimeSources) { InputStream mimeInput = mimeSource.getMimeSource().getInputStream(); MimeMessage message = retainMessageID ? new MimeMessageWithID(MailSession.getDefaultSession(), mimeInput, sourceMessage.getMessageID()) : new MimeMessage(MailSession.getDefaultSession(), mimeInput); sendNewMessage(mail, mimeSource.getRecipients(), message, handledProcessor); } /* * Send email to all the recipients that need the message unchanged */ Set<MailAddress> asIsRecipients = messages.getAsIsRecipients(); if (CollectionUtils.isEmpty(asIsRecipients)) { /* * We no longer need the original Mail so ghost it */ mail.setState(Mail.GHOST); } else { mail.setRecipients(asIsRecipients); } } finally { messages.close(); } } } } catch (DatabaseException e) { getLogger().error("Error handling the message.", e); } catch (MessagingException e) { getLogger().error("Error handling the message.", e); } catch (IOException e) { getLogger().error("Error handling the message.", e); } } private void certificatesEvent(Collection<? extends X509Certificate> certificates) throws CertificateException { if (importCertificates) { for (X509Certificate certificate : certificates) { try { keyAndCertificateWorkflow.addCertificateTransacted(certificate); } catch (CertStoreException e) { getLogger().error("Error adding certificate. Skipping this certificate.", e); } } } } private Messages handleMessageTransacted(Mail mail) throws DatabaseException { try { return isStrict(mail) ? handleMessageTransactedStrict(mail) : handleMessageTransactedNonStrict(mail); } catch (MessagingException e) { throw new DatabaseException(e); } } private Messages handleMessageTransactedNonStrict(Mail mail) throws MessagingException { MimeMessage sourceMessage = mail.getMessage(); RecursiveSMIMEHandler recursiveSMIMEHandler = new RecursiveSMIMEHandler(pKISecurityServices); recursiveSMIMEHandler.setAddInfo(addInfo); recursiveSMIMEHandler.setDecrypt(decrypt); recursiveSMIMEHandler.setDecompress(decompress); recursiveSMIMEHandler.setMaxRecursion(maxRecursion); recursiveSMIMEHandler.setRemoveSignature(isRemoveSignatureAttribute(mail)); recursiveSMIMEHandler.setProtectedHeaders(protectedHeaders); recursiveSMIMEHandler.setCertificatesEventListener(certificateCollectionEvent); /* * Use a different SMIMEInfoHandler so we can add tags (like [encrypt]) to the subject of the message */ recursiveSMIMEHandler.setSmimeInfoHandler(new ExtSMIMEInfoHandlerImpl(mail)); MimeMessage handledMessage; try { handledMessage = recursiveSMIMEHandler.handlePart(sourceMessage); } catch (SMIMEHandlerException e) { throw new MessagingException("Error handling message.", e); } Messages messages = new Messages(threshold); try { List<MailAddress> recipients = MailAddressUtils.getRecipients(mail); if (handledMessage != null) { messages.addMessage(handledMessage, recipients); } else { /* * The message was not encrypted, could not be decrypted etc. */ messages.addAsIsRecipient(recipients); } } catch (Exception e) { /* * We need to catch all to make sure messages will always be closed. * If not there is a change that temp files won't be deleted. */ messages.close(); if (e instanceof RuntimeException) { throw (RuntimeException) e; } if (e instanceof MessagingException) { throw (MessagingException) e; } throw new MessagingException("Error handling user", e); } return messages; } /* * Returns all the private keys that are selected for the user. The private keys are the automatically * selected keys (i.e., matching email address and valid), the manually selected and the inherited. */ private Set<KeyAndCertificate> getKeyAndCertificates(User user) throws MessagingException { try { Set<KeyAndCertificate> keys = new HashSet<KeyAndCertificate>(); Set<X509Certificate> certificates = new HashSet<X509Certificate>(); encryptionRecipientSelector.select(Collections.singleton(user), certificates); KeyAndCertStore keyAndCertStore = pKISecurityServices.getKeyAndCertStore(); for (X509Certificate certificate : certificates) { X509CertStoreEntry entry = keyAndCertStore.getByCertificate(certificate); if (entry != null) { KeyAndCertificate keyAndCertificate = keyAndCertStore.getKeyAndCertificate(entry); if (keyAndCertificate != null && keyAndCertificate.getPrivateKey() != null) { keys.add(keyAndCertificate); } } } return keys; } catch (CertStoreException e) { throw new MessagingException("Error getting key and certificates for user: " + user.getEmail(), e); } catch (KeyStoreException e) { throw new MessagingException("Error getting key and certificates for user: " + user.getEmail(), e); } catch (HierarchicalPropertiesException e) { throw new MessagingException("Error getting key and certificates for user: " + user.getEmail(), e); } } private MimeMessage handleMessageForUser(Mail mail, User user) throws MessagingException { MimeMessage handledMessage = null; Set<KeyAndCertificate> keys = getKeyAndCertificates(user); if (logger.isDebugEnabled()) { logger.debug("Nr of keys: " + keys.size()); } RecursiveSMIMEHandler recursiveSMIMEHandler = new RecursiveSMIMEHandler( new StaticKeysPKISecurityServices(pKISecurityServices, keys)); recursiveSMIMEHandler.setAddInfo(addInfo); recursiveSMIMEHandler.setDecrypt(decrypt); recursiveSMIMEHandler.setDecompress(decompress); recursiveSMIMEHandler.setMaxRecursion(maxRecursion); recursiveSMIMEHandler.setRemoveSignature(isRemoveSignatureAttribute(mail)); recursiveSMIMEHandler.setProtectedHeaders(protectedHeaders); recursiveSMIMEHandler.setCertificatesEventListener(certificateCollectionEvent); /* * Use a different SMIMEInfoHandler so we can add tags (like [encrypt]) to the subject of the message */ recursiveSMIMEHandler.setSmimeInfoHandler(new ExtSMIMEInfoHandlerImpl(mail)); try { handledMessage = recursiveSMIMEHandler.handlePart(mail.getMessage()); } catch (SMIMEHandlerException e) { logger.error("Error checking for S/MIME for user " + user.getEmail()); } catch (MessagingException e) { logger.error("Error checking for S/MIME for user " + user.getEmail()); } return handledMessage; } private Messages handleMessageTransactedStrict(Mail mail) throws MessagingException { Messages messages = new Messages(threshold); try { List<MailAddress> recipients = MailAddressUtils.getRecipients(mail); for (MailAddress recipient : recipients) { if (recipient == null) { continue; } MimeMessage handledMessage = null; try { handledMessage = handleMessageForUser(mail, userWorkflow.getUser(recipient.toString(), GetUserMode.CREATE_IF_NOT_EXIST)); } catch (AddressException e) { throw new MessagingException("Error strict handling message for user: " + recipient, e); } catch (HierarchicalPropertiesException e) { throw new MessagingException("Error strict handling message for user: " + recipient, e); } if (handledMessage != null) { messages.addMessage(handledMessage, recipient); } else { /* * The message was not encrypted, could not be decrypted etc. */ messages.addAsIsRecipient(recipient); } } return messages; } catch (Exception e) { /* * We need to catch all to make sure messages will always be closed. * If not there is a change that temp files won't be deleted. */ messages.close(); if (e instanceof RuntimeException) { throw (RuntimeException) e; } if (e instanceof MessagingException) { throw (MessagingException) e; } throw new MessagingException("Error handling user", e); } } /* * Stores the message source and the recipients of the message. */ private static class MimeSource { /* * Buffer that stores the raw mime source of the message. If the number of bytes * written to ReadableOutputStreamBuffer exceeds the threshold, the bytes are * written to a temporary file. */ final ReadableOutputStreamBuffer mimeSource; /* * The recipients of the message */ final Set<MailAddress> recipients = new HashSet<MailAddress>(); MimeSource(ReadableOutputStreamBuffer mimeSource) { this.mimeSource = mimeSource; } Set<MailAddress> getRecipients() { return recipients; } ReadableOutputStreamBuffer getMimeSource() { return mimeSource; } } /* * Keeps track of all the resuling messages when messages are being handled by the * RecursiveSMIMEHandler. */ private static class Messages { /* * The memory threshold of the buffer */ final int threshold; /* * Mapping from SHA1 hash of the message to the message and recipients for the message */ final Map<String, MimeSource> digestedMessages = new HashMap<String, MimeSource>(); /* * Set of recipients to which the message should be send as-is, i.e., the message * was not an S/MIME message or the message could not be decrypted for the * recipient */ final Set<MailAddress> asIsRecipients = new HashSet<MailAddress>(); /* * Keep track of all the buffers that must be closed */ final Set<ReadableOutputStreamBuffer> buffers = new HashSet<ReadableOutputStreamBuffer>(); Messages(int threshold) { this.threshold = threshold; } /* * Returns a message digest used to calculate the hash of the message */ MessageDigest createDigest() throws IOException { try { return SecurityFactoryFactory.getSecurityFactory().createMessageDigest("SHA1"); } catch (Exception e) { throw new IOException(e); } } /* * Returns the available threshold. */ int getThreshold() { long newThreshold = this.threshold; /* * calculate the available threshold based on the number of bytes * currently used by the buffers */ for (ReadableOutputStreamBuffer buffer : buffers) { /* * If byte count of the buffer exceeds, */ newThreshold = newThreshold - buffer.getByteCount(); if (newThreshold < 0) { newThreshold = 0; break; } } return (int) newThreshold; } void releaseBuffer(ReadableOutputStreamBuffer buffer) { IOUtils.closeQuietly(buffer); buffers.remove(buffer); } /* * Closes all buffers. * * Note: close should always be called to make sure that all temporary files are cleaned up */ void close() { for (ReadableOutputStreamBuffer buffer : buffers) { IOUtils.closeQuietly(buffer); } } MimeSource addMessage(MimeMessage message) throws MessagingException, IOException { message.saveChanges(); ReadableOutputStreamBuffer mime = new ReadableOutputStreamBuffer(getThreshold()); /* * Store a reference to make sure we can always close the buffers even * when an exception has occurred. */ buffers.add(mime); /* * Calculate a SHA1 of the message content so we can check wether this message * is the same message as another message. The reason for doing this is that * when in strict mode, a message with multiple recipents is decrypted for * each recipient. If the resulting message is the same, we do need to * create multiple message but can add the recipient as a recipient of that * particular message. This is an optimization because with domain encryption * it can happen that a message has hundreds of recipients all encrypted with * the same key. We do not want such a message to be exploded into hundreds * of separate messages. */ DigestOutputStream digestOutputStream = new DigestOutputStream(new NullOutputStream(), createDigest()); /* * We need to split the mime output because we only want to calculate the SHA1 * of the message body. The main reason for this is that saveChanges will add * a new message-id for each message. */ TeeOutputStream output = new TeeOutputStream(new BufferedOutputStream(mime), new SkipHeadersOutputStream(digestOutputStream)); message.writeTo(output); /* * We cannot close the mime stream yet because we need to be able to read the * content. We therefore need to flush all buffers. */ digestOutputStream.flush(); String messageDigest = HexUtils.hexEncode(digestOutputStream.getMessageDigest().digest()); MimeSource mimeSource = digestedMessages.get(messageDigest); if (mimeSource == null) { mimeSource = new MimeSource(mime); digestedMessages.put(messageDigest, mimeSource); } else { /* * We don't need to keep the mime content because the mime content * has already been stored. We will release it to save memory. */ releaseBuffer(mime); } return mimeSource; } void addMessage(MimeMessage message, Collection<MailAddress> recipients) throws MessagingException { try { addMessage(message).getRecipients().addAll(recipients); } catch (IOException e) { throw new MessagingException("Error adding message.", e); } } void addMessage(MimeMessage message, MailAddress recipient) throws MessagingException { try { addMessage(message).getRecipients().add(recipient); } catch (IOException e) { throw new MessagingException("Error adding message.", e); } } void addAsIsRecipient(Collection<MailAddress> recipients) { asIsRecipients.addAll(recipients); } void addAsIsRecipient(MailAddress recipient) { asIsRecipients.add(recipient); } Collection<MimeSource> getMimeSources() { return digestedMessages.values(); } public Set<MailAddress> getAsIsRecipients() { return asIsRecipients; } } /* * Extension of SMIMEInfoHandlerImpl which is used for adding security information to the subject */ private class ExtSMIMEInfoHandlerImpl extends SMIMEInfoHandlerImpl { /* * The tags that will be added to the subject (can be null) */ private final SecurityInfoTags tags; /* * The MailID of the Mail */ private final String mailID; public ExtSMIMEInfoHandlerImpl(Mail mail) { super(pKISecurityServices); DjigzoMailAttributes mailAttributes = DjigzoMailAttributesImpl.getInstance(mail); tags = mailAttributes.getSecurityInfoTags(); mailID = mailAttributes.getMailID(); } private void addTagToSubject(MimeMessage message, String tag) { try { String currentSubject = message.getSubject(); String newSubject = String.format(subjectTemplate, StringUtils.defaultString(currentSubject), StringUtils.defaultString(tag)); message.setSubject(newSubject); } catch (Exception e) { logger.error("Error while appending text to subject"); } } private boolean isSenderMismatch(MimeMessage message, Set<String> signers) { /* * Get the first From header and canonicalize it */ String canonicalizedFrom = EmailAddressUtils.canonicalize(EmailAddressUtils .getEmailAddress(EmailAddressUtils.getAddress(EmailAddressUtils.getFromQuietly(message)))); if (signers != null) { for (String signer : signers) { if (StringUtils.equals(canonicalizedFrom, EmailAddressUtils.canonicalize(signer))) { /* * On of the signers is equals to the from so no mismatch */ return false; } } } return true; } @Override protected void onSigned(MimeMessage message, SMIMEInspector sMIMEInspector, int level, boolean signatureValid, Set<String> signers) throws MessagingException { if (tags == null) { /* * there is nothing to add */ return; } String tag; if (signatureValid) { /* * If there is a mismatch between "sender" and email address from certificate, show the * email address(es) of the signer(s). */ if (isSenderMismatch(message, signers)) { /* * There is a mismatch between the from and the signers. We will therefore add the signers * to the subject */ try { tag = String.format(tags.getSignedByValidTag(), StringUtils.defaultString(StringUtils.join(signers, ", "))); } catch (Exception e) { logger.error("Invalid format string", e); tag = "<invalid format string>"; } } else { tag = tags.getSignedValidTag(); } } else { tag = tags.getSignedInvalidTag(); } addTagToSubject(message, tag); } @Override protected void onEncrypted(MimeMessage message, SMIMEInspector sMIMEInspector, int level, boolean decrypted) throws MessagingException { /* * Log the decryption event (see https://jira.djigzo.com/browse/GATEWAY-42). * * Note: we will also log the MailID otherwise it's unclear for which message the log entry is */ if (decrypted) { logger.info("Message with MailID {} has been decrypted.", StringUtils.defaultString(mailID)); } if (tags == null) { /* * there is nothing to add */ return; } if (decrypted) { addTagToSubject(message, tags.getDecryptedTag()); } } } }