SMIME Mail Signing and Encryption using SMTP, IMAP - Yash-777/java-utils-mail-smime GitHub Wiki
Example
package com.java.mail;
import java.io.*;
import java.security.*;
import java.text.*;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.*;
import javax.mail.internet.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.java.mail.common.ByteArrayDataSource;
import com.sun.mail.smtp.SMTPTransport;
import net.markenwerk.utils.mail.smime.SmimeKey;
import net.markenwerk.utils.mail.smime.SmimeState;
import net.markenwerk.utils.mail.smime.SmimeUtil;
public class SMIMEMial_Util {
private final static Log log = LogFactory.getLog(SMIMEMial_Util.class);
// Sign with PrivateKey(Include PrivateKey Public Certificate), Encrypt with Public Key
public static String security_privatekey_Sign = "Baeldung.p12", passwordCert_Sign ="password",
security_certificate_Encrypt = "Baeldung.cer";
public static String security_privatekey_DEcrypt = "Baeldung.p12", passwordCert_Decrypt ="password",
security_certificate_VerifySign = "Baeldung.cer";
static String outputMailFile = "C:/Yash/Mail/SendingMessage_<CurrDate>.eml";// .eml .msg .txt
static String emlReadFile = "C:/Yash/Mail/SendingMessage_20210212_094108.eml"; // .msg, .eml
public boolean isSigned, isEncrypted, isStoreMessage;
public SMIMEMial_Util (boolean isSigned, boolean isEncrypted, boolean isStoreMessage) {
this.isSigned = isSigned; this.isEncrypted = isEncrypted; this.isStoreMessage = isStoreMessage;
}
public X509Certificate cert, recipientPrivateKeyCert;
public PrivateKey recipientPrivateKey;
public void setCets(InputStream privateKeyStream, String passwordCert, InputStream certStream) throws Exception {
if (isEncrypted) {
if (null == certStream) throw new Exception("Empty Stream for Encrypt Mail");
cert = getX509Certificate(certStream);
}
if (isSigned) {
if (null == privateKeyStream) {
throw new Exception("Empty Stream to Signing Mail");
} else if (passwordCert == null || passwordCert == "") {
throw new Exception("Empty password to Read the Private Key to Signing Mail");
}
recipientPrivateKey = getPrivateCertificate(privateKeyStream, passwordCert);
}
}
public void setCets_IMAP(InputStream privateKeyStream, String passwordCert, InputStream certStream) throws Exception {
if (isEncrypted) {
if (null == privateKeyStream) {
throw new Exception("Empty Stream to Signing Mail");
} else if (passwordCert == null || passwordCert == "") {
throw new Exception("Empty password to Read the Private Key to Signing Mail");
}
recipientPrivateKey = getPrivateCertificate(privateKeyStream, passwordCert);
}
if (isSigned) {
if (null == certStream) throw new Exception("Empty Stream for Encrypt Mail");
cert = getX509Certificate(certStream);
}
}
private static DateFormat dateFormat = null;
private static String reportDate, timeZone = "CET";;
private static final String DATEPATTERN_STR = "yyyyMMdd_HHmmss";
public Session session_IMAP;
String sftpHost, sftpPort;
boolean isSmtpAuth, doSSL;
String smtpAuthLogin, smtpAuthPassword, SSLSOCKETFACTORYClass;
public void setProperties(String sftpHost, String sftpPort,
boolean isSmtpAuth, String userLogin, String userPassword,
boolean doSSL, String SSLSOCKETFACTORYClass) {
this.sftpHost = sftpHost;
this.sftpPort = sftpPort;
this.isSmtpAuth = isSmtpAuth;
this.smtpAuthLogin = userLogin; this.smtpAuthPassword = userPassword;
this.doSSL = doSSL;
this.SSLSOCKETFACTORYClass = SSLSOCKETFACTORYClass;
}
public Properties getPropertiesSFTP() {
Properties props = new Properties();
props.put("mail.smtp.host", sftpHost);
props.put("mail.smtp.port", sftpPort);
if (isSmtpAuth) {
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.user", smtpAuthLogin);
props.put("mail.smtp.password", smtpAuthPassword);
}
if (doSSL) {
props.put("mail.smtp.socketFactory.class", SSLSOCKETFACTORYClass);
props.put("mail.smtp.socketFactory.fallback", "false");
}
return props;
}
public static Properties getPropertiesIMAP(String protocol, int port) {
Properties props = new Properties();
props = System.getProperties();
if (protocol.equalsIgnoreCase("IMAP") || protocol.equalsIgnoreCase("IMAPS")) {
props.setProperty("mail.imap.partialfetch", "false");
if (protocol.equalsIgnoreCase("IMAPS")) {
props.setProperty("mail.imap.ssl.enable", "true");
} else {
props.setProperty("mail.imap.ssl.enable", "false");
}
// set this session up to use SSL for IMAP connections
props.setProperty("mail.imap.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
// don't fallback to normal IMAP connections on failure.
props.setProperty("mail.imap.socketFactory.fallback", "false");
props.setProperty("mail.imap.socketFactory.port", port + "");// S/IMAP port for imap/ssl connections.
props.setProperty("mail.imap.auth.plain.disable", "true");
//props.setProperty("mail.imaps.auth.plain.disable", "true");
}
return props;
}
public Message[] getMessages(boolean isStoreMessage,
String protocol, String server, int port, String userName, String password, String mailBoxFolder, int readCount) throws MessagingException, FileNotFoundException {
if (isStoreMessage) { // Default Mail Server properties
File emlFile = new File(emlReadFile);
InputStream source = new FileInputStream(emlFile);
Properties props = System.getProperties();
Session session_IMAP = Session.getDefaultInstance(props, null);
MimeMessage message_IMAP = new MimeMessage(session_IMAP, source);
this.session_IMAP = session_IMAP;
Message[] messages = new Message[1];
messages[0] = message_IMAP;
return messages;
} else { // Mail Server Connection properties
Properties props = getPropertiesIMAP(protocol, 993); // System.getProperties();
Session session_IMAP = Session.getDefaultInstance(props, null);
session_IMAP.setDebug(true);
this.session_IMAP = session_IMAP;
Store store = session_IMAP.getStore(protocol.toLowerCase());
store.connect(server, userName, password);
Folder inboxFolder = store.getFolder( mailBoxFolder );
inboxFolder.open(Folder.READ_WRITE);
Message[] messages = inboxFolder.getMessages();
if (store.isConnected()) {
System.out.println("*******************imap store connected*****************");
}
return messages;
}
}
public void setCertificates(boolean isClassPath, String security_privatekey, String passwordCert, String security_certificate, String protocol) {
try {
InputStream privateStream = getCerFileStream(isClassPath, security_privatekey);
InputStream certStream = getCerFileStream(isClassPath, security_certificate);
if (protocol.equalsIgnoreCase("IMAP") || protocol.equalsIgnoreCase("IMAPS")) {
setCets_IMAP(privateStream, passwordCert, certStream);
} else {
setCets(privateStream, passwordCert, certStream);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
boolean isEncrypt = true, isSign = false;
SMIMEMial_Util classObj = new SMIMEMial_Util(isSign, isEncrypt, true);
classObj.setCertificates(true, security_privatekey_Sign, passwordCert_Sign, security_certificate_Encrypt, "SFTP");
SmimeKey mimeKey = new SmimeKey(classObj.recipientPrivateKey, classObj.cert);
// ===== --- SMTP --- =====
String fromAddr = "[email protected]",
toAddr = "[email protected];[email protected]",
host = "smtpmail.domain.onmicrosoft.com", portSMTP = "25";
classObj.setProperties(host, portSMTP, false, null, null, false, null);
Properties props_SMTP = classObj.getPropertiesSFTP();
Session session_SMTP = Session.getInstance(props_SMTP);
String mailSubject = "Test Mail SMIME";
String bodyText = "Sample String Body Content", bodyType = "text/plain; charset=utf-8";
List<AttachmentFilesSMTP> sampleAttachementData = classObj.getSampleAttachementData();
classObj.mailSendSFTP(mimeKey, fromAddr, toAddr, mailSubject, session_SMTP, sampleAttachementData, bodyText, bodyType);
// ===== --- IMAP --- =====
SMIMEMial_Util classObj_IMAP = new SMIMEMial_Util(isSign, isEncrypt, true);
classObj_IMAP.setCertificates(true, security_privatekey_DEcrypt, passwordCert_Decrypt, security_certificate_Encrypt, "IMAP");
SmimeKey mimeKey_IMAP = new SmimeKey(classObj_IMAP.recipientPrivateKey, classObj_IMAP.cert);
String protocol = "imap", server = "outlook.office365.com", userName = "[email protected]", password = "12345", mailBoxFolder = "Mail Test";
int portIMAP = 993, readCount = 2;
Message[] messages = classObj_IMAP.getMessages(classObj_IMAP.isStoreMessage, protocol, server, portIMAP, userName, password, mailBoxFolder, readCount);
List<MailParts> partsInfo = new ArrayList<MailParts>();
for (int i = 0; i < messages.length; i++) {
MailParts partsObj = new MailParts();
MimeMessage message_IMAP = (MimeMessage) messages[i];
log.info("MSG: "+ message_IMAP);
MailParts objPartInfo = classObj_IMAP.readMailIMAP(mimeKey_IMAP, classObj_IMAP.session_IMAP, message_IMAP, partsObj);
partsInfo.add(objPartInfo);
log.info("Obj :"+objPartInfo);
}
System.out.println("partsInfo: "+partsInfo);
}
MailParts objParts;
public void setMailObj(MailParts obj) {
this.objParts = obj;
}
public MailParts readMailIMAP(SmimeKey mimeKey, Session session_IMAP, MimeMessage message_IMAP, MailParts partsObj) {
log.info(" ===== --- IMAP --- ===== ");
try {
setMailObj(partsObj);
fillMailDetails(message_IMAP);
getMailParts(session_IMAP, message_IMAP, mimeKey);
} catch (Exception e) {
e.printStackTrace();
}
return objParts;
}
public List<AttachmentFilesSMTP> getSampleAttachementData() throws IOException {
List<AttachmentFilesSMTP> attachments = new ArrayList<AttachmentFilesSMTP>();
String fileName = "MailSampleAttachement_"+reportDate+".csv";
if (fileName != null) {
AttachmentFilesSMTP att = new AttachmentFilesSMTP();
att.setFileName(fileName);
att.setContentType("text/csv");
ByteArrayOutputStream outStream = att.getOutStream();
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outStream);
for (int i = 0; i < 5; i++) {
bufferedOutputStream.write("CSV DATA\n".getBytes());
} bufferedOutputStream.flush();
attachments.add(att);
}
return attachments;
}
public void mailSendSFTP(SmimeKey mimeKey, String fromAddr, String toAddr, String mailSubject, Session session_SMTP,
List<AttachmentFilesSMTP> attachments, String bodyText, String bodyType) {
log.info(" ===== --- SMTP --- ===== Session:"+session_SMTP);
try {
MimeMessage message_SMTP = new MimeMessage(session_SMTP);
log.info("message_SMTP:"+message_SMTP);
message_SMTP.setFrom(new InternetAddress(fromAddr));
if (toAddr.contains(";")) {
String[] toAddressList = toAddr.split(";");
InternetAddress[] mailAddress_TO = new InternetAddress[ toAddressList.length ];
for (int i = 0; i < toAddressList.length; i++) {
mailAddress_TO[i] = new InternetAddress( toAddressList[i] );
}
message_SMTP.setRecipients(RecipientType.TO, mailAddress_TO);
} else {
message_SMTP.setRecipient(RecipientType.TO, new InternetAddress(toAddr));
}
message_SMTP.setSubject(mailSubject);
message_SMTP.setSentDate(new Date());
if (attachments != null && attachments.size() > 0) {
Multipart multipartContent = new MimeMultipart();
for (Iterator<AttachmentFilesSMTP> it = attachments.iterator(); it.hasNext();) {
AttachmentFilesSMTP attachment = (AttachmentFilesSMTP) it.next();
InputStream is = attachment.getContent();
DataSource attachementDataSource = new ByteArrayDataSource(is, attachment.getContentType(), attachment.getFileName());
MimeBodyPart attachmentMimeBody = new MimeBodyPart();
attachmentMimeBody.setDataHandler(new DataHandler(attachementDataSource));
attachmentMimeBody.setFileName(attachementDataSource.getName());
multipartContent.addBodyPart(attachmentMimeBody);
}
if (bodyText != null) {
MimeBodyPart textBodyPart = new MimeBodyPart();
textBodyPart.setContent(bodyText, bodyType);
// Add inline image attachments : https://www.codejava.net/java-ee/javamail/embedding-images-into-e-mail-with-javamail
multipartContent.addBodyPart(textBodyPart);
}
message_SMTP.setContent(multipartContent);
} else {
message_SMTP.setContent(bodyText, bodyType);
}
log.info("message_SMTP setContent competed");
if (isSigned) {
SmimeKey mimeKeySign = new SmimeKey(mimeKey.getPrivateKey(), this.recipientPrivateKeyCert);
MimeMessage signMessage = signMessage(session_SMTP, message_SMTP, fromAddr, mimeKeySign);
message_SMTP = signMessage;
}
if (isEncrypted) {
MimeMessage encryptedSignedMessage = encryptMessage(session_SMTP, message_SMTP, toAddr, mimeKey);
message_SMTP = encryptedSignedMessage;
}
if (isStoreMessage) { // Save Message to File
outputMailFile = outputMailFile.replace("<CurrDate>", reportDate);
message_SMTP.writeTo(System.out);
message_SMTP.writeTo(new FileOutputStream(outputMailFile));
emlReadFile = outputMailFile;
} else { // Send Mail over Network
SMTPTransport transport = (SMTPTransport) session_SMTP.getTransport("smtp");
try {
if (isSmtpAuth) { // "mail.smtp.port", "25"
transport.connect(sftpHost, Integer.valueOf(sftpPort), smtpAuthLogin, smtpAuthPassword);
} else {
transport.connect();
}
transport.sendMessage(message_SMTP, message_SMTP.getAllRecipients());
} finally {
transport.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void fillMailDetails(MimeMessage message_IMAP) throws MessagingException {
Address[] from = message_IMAP.getFrom();
objParts.setForm( getMailAddress(from));
Address[] replyTo = message_IMAP.getAllRecipients();
objParts.setTo( getMailAddress(replyTo));
objParts.setSubject(message_IMAP.getSubject());
}
public static String getMailAddress(Address[] mailIds) {
StringBuffer buff = new StringBuffer();
for (int i = 0; i < mailIds.length; i++) {
buff.append(mailIds[i]);
if ( (i + 1) < mailIds.length) buff.append(";");
}
return buff.toString();
}
public void getMailParts(Session session_IMAP, MimeMessage message_IMAP, SmimeKey mimeKey) throws IOException, MessagingException {
Object content = message_IMAP.getContent();
if (content instanceof MimeMultipart) { // Signed Part comes under Multipart/mixed.
log.info("===== MimeMultipart Content ----- MailParts");
MimeMultipart multipart = (MimeMultipart) message_IMAP.getContent();
fillMultiparts(multipart);
} else if (content instanceof String) {
log.info("===== String Content");
String bodyString = (String) content;
objParts.setBodyContent( bodyString.length() > 4000 ? bodyString.substring(0, 4000) : bodyString );
} else { // Encrypted Mail part
// String mimeType = message_IMAP.getHeader("Content-type", ";");
String contentType = displayHeadersGetContentType(message_IMAP);
log.info("===== S/MIME ContentType :"+contentType);
SmimeState smimeState = net.markenwerk.utils.mail.smime.SmimeUtil.getStatus(message_IMAP);
log.info("===== S/MIME SmimeUtil ContentType ::"+smimeState);
if ( contentType.contains("enveloped-data") || (smimeState == SmimeState.ENCRYPTED) ) {
objParts.setEncrypted(true);
SmimeKey mimeKeyDecrypt = new SmimeKey(mimeKey.getPrivateKey(), this.recipientPrivateKeyCert);
MimeMessage decryptedMessage = SmimeUtil.decrypt(session_IMAP, message_IMAP, mimeKeyDecrypt);
log.info("decryptedMessage:"+decryptedMessage);
getMailParts(session_IMAP, decryptedMessage, mimeKey);
}
/*if (smimeState == SmimeState.SIGNED) { // When mail only Signed
obj.setSigned(true);
boolean validSignature = SmimeUtil.checkSignature(message_IMAP);
log.info("Mail Entire Signature Status : "+ validSignature);
obj.setSignatureValid(validSignature);
MimeBodyPart signedContent = SmimeUtil.getSignedContent(message_IMAP);
//fillMultiparts( (MimeMultipart) signedContent);
}*/
}
}
public void fillBodyParts(BodyPart bp) throws MessagingException, IOException {
String contentType = bp.getContentType();
log.info("===== BodyPart ContentType :"+contentType);
String attachmentName = bp.getFileName();
InputStream attachmentDataStream = bp.getInputStream();
if (attachmentName != null && attachmentName.length() > 0) {
objParts.addPart(attachmentName, attachmentDataStream);
} else {
BufferedReader reader = new BufferedReader(new InputStreamReader(attachmentDataStream, "UTF-8"));
StringBuffer sb = new StringBuffer();
while (reader.ready()) {
sb.append(reader.readLine());
sb.append(System.getProperty("line.separator"));
}
String bodyString = sb.toString();
objParts.setBodyContent( bodyString.length() > 4000 ? bodyString.substring(0, 4000) : bodyString );
}
}
public void fillMultiparts(MimeMultipart multipart) throws IOException, MessagingException {
int countParts = multipart.getCount();
objParts.setMimeMultipart(true);
String multipartCount = objParts.getMimeMultipartCount();
if (multipartCount == "") {
objParts.appendMimeMultipartCount("Parts: "+countParts);
} else {
objParts.appendMimeMultipartCount("~ Parts: "+countParts);
}
for (int j = 0; j < countParts; j++) {
BodyPart bp = multipart.getBodyPart(j);
String contentType = bp.getContentType();
log.info("===== Multipart ContentType :"+contentType);
log.info("BodyPart LineCount "+ bp.getLineCount());
if (contentType.contains("smime-type=signed-data")) {
SmimeState smimeState = net.markenwerk.utils.mail.smime.SmimeUtil.getStatus(multipart);
log.info("===== S/MIME Multipart ContentType ::"+smimeState);
if (smimeState == SmimeState.SIGNED) { // When mail only Signed
objParts.setSigned(true);
boolean validSignature = SmimeUtil.checkSignature(multipart);
log.info("Signature Status : "+ validSignature);
objParts.setSignatureValid(validSignature);
}
String attachmentName = bp.getFileName();
InputStream attachmentDataStream = bp.getInputStream();
if (attachmentName != null && attachmentName.length() > 0) {
objParts.addPart(attachmentName, attachmentDataStream);
}
} else if (contentType.contains("multipart/mixed;")) {
Object content = bp.getContent();
if (content instanceof MimeMultipart) {
log.info("===== BodyPart MimeMultipart Content");
MimeMultipart multipartBody = (MimeMultipart) content;
fillMultiparts(multipartBody);
} else if (content instanceof String) {
log.info("===== BodyPart String Content");
String bodyString = (String) content;
objParts.setBodyContent( bodyString.length() > 4000 ? bodyString.substring(0, 4000) : bodyString );
}
} else {
fillBodyParts(bp);
}
}
}
private MimeMessage signMessage(Session session, MimeMessage message, String from, SmimeKey smimeKey) throws Exception {
return SmimeUtil.sign(session, message, smimeKey);
}
private MimeMessage encryptMessage(Session session, MimeMessage message, String to, SmimeKey smimeKey) throws Exception {
return SmimeUtil.encrypt(session, message, smimeKey.getCertificate());
}
public String displayHeadersGetContentType(MimeMessage message) throws MessagingException {
String contentType = "";
Enumeration<Header> enumer = message.getAllHeaders();
log.info("===== Headers Start");
while (enumer.hasMoreElements()) {
Header header = (Header) enumer.nextElement();
System.out.printf("%s ~:~ %s%n", header.getName(), header.getValue());
if (header.getName().equalsIgnoreCase("Content-Type")) {
contentType = header.getValue();
log.info("ContentType : "+contentType);
}
}
log.info("===== Headers End");
return contentType;
}
private PrivateKey getPrivateCertificate(InputStream privateKeyStream, String passwordCert) {
PrivateKey recipientPrivateKey = null;
try {
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(privateKeyStream, passwordCert.toCharArray());
Enumeration<String> aliases = ks.aliases();
String alias = null;
while (aliases.hasMoreElements()) {
alias = aliases.nextElement(); // Alias: Baeldung
log.info("Certificate Alias: "+ alias);
}
recipientPrivateKey = (PrivateKey) ks.getKey(alias, passwordCert.toCharArray());
X509Certificate recipientCert = (X509Certificate) ks.getCertificate(alias);
this.recipientPrivateKeyCert = recipientCert;
return recipientPrivateKey;
} catch (KeyStoreException e) {
log.error(e, e);
} catch (NoSuchAlgorithmException e) {
log.error(e, e);
} catch (CertificateException e) {
log.error(e, e);
} catch (IOException e) {
log.error(e, e);
} catch (UnrecoverableKeyException e) {
log.error(e, e);
}
return recipientPrivateKey;
}
private X509Certificate getX509Certificate(InputStream inputStream) {
X509Certificate x509Certificate = null;
CertificateFactory certificateFactory = null;
try {
certificateFactory = CertificateFactory.getInstance("X.509", "BC");
x509Certificate = (X509Certificate) certificateFactory.generateCertificate(inputStream);
} catch (CertificateException e) {
log.error(e, e);
} catch (NoSuchProviderException e) {
log.error(e, e);
}
return x509Certificate;
}
static {
dateFormat = new SimpleDateFormat(DATEPATTERN_STR);
TimeZone cetTime = TimeZone.getTimeZone(timeZone);
dateFormat.setTimeZone(cetTime);
reportDate = dateFormat.format(new Date());
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
}
public InputStream getCerFileStream(boolean isClassPath, String fileName) throws FileNotFoundException {
InputStream stream = null;
if (isClassPath) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
stream = classLoader.getResourceAsStream(fileName);
} else {
stream = new FileInputStream(fileName);
}
return stream;
}
}
class AttachmentFilesSMTP {
private String fileName, contentType;
private ByteArrayOutputStream outStream = new ByteArrayOutputStream();
public byte[] getByteArray() {
return outStream.toByteArray();
}
public InputStream getContent() throws Exception {
return new ByteArrayInputStream(getByteArray());
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public ByteArrayOutputStream getOutStream() {
return outStream;
}
public void setOutStream(ByteArrayOutputStream outStream) {
this.outStream = outStream;
}
}
log4j.properties
# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=DEBUG, RFile, console
log4j.logger.com.demo.package=debug,console
log4j.additivity.com.demo.package=false
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.target=System.out
log4j.appender.console.immediateFlush=true
log4j.appender.console.encoding=UTF-8
log4j.appender.console.threshold=info
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.conversionPattern=%d [%t] %-5p %c - %m%n
# File based log output
log4j.appender.RFile.File=${catalina.home}/logs/MyAppDashboard.log
log4j.appender.RFile=org.apache.log4j.RollingFileAppender
log4j.appender.RFile.MaxFileSize=10000KB
# Keep 80 backup files
log4j.appender.RFile.MaxBackupIndex=80
log4j.appender.RFile.layout=org.apache.log4j.PatternLayout
log4j.appender.RFile.layout.ConversionPattern= %5p\t[%d] [%t] (%F:%L) %c \n \t%m%n\n