Uncategorized

JavaMailでSMTPS/STARTTLSを使う場合はmail.smtp.ssl.checkserveridentityを設定する必要がある

まとめ

JavaMailでSMTPS/STARTLSを使う場合は “mail.smtp.ssl.checkserveridentity” を明示的に “true” に設定しましょう。デフォルトではホスト名検証されません。

JavaMailとは

JavaMailはJavaからメールの送受信を行うためのAPIです。

たとえば以下のようなコードでGMailのSMTPサーバを使ってメールを送信することができます。

import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.io.UnsupportedEncodingException;
import java.util.Properties;


void main() throws MessagingException, UnsupportedEncodingException {
    Properties props = getProperties();
    Session session = Session.getInstance(props, new Authenticator() {
        protected PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication("username@example.com", "password");
        }
    });
    String encoding = "UTF-8";
    MimeMessage mimeMessage = new MimeMessage(session);
    mimeMessage.setHeader("Content-Type", STR."text/plain; charset=\{encoding}");
    mimeMessage.setFrom(new InternetAddress("from@example.com", "from@example.com", encoding));
    mimeMessage.setRecipients(Message.RecipientType.TO, new Address[]{new InternetAddress("to@example.com")});
    mimeMessage.setRecipients(Message.RecipientType.CC, "cc@example.com");
    mimeMessage.setRecipients(Message.RecipientType.BCC
            , new Address[]{new InternetAddress("bcc@example.com")});
    mimeMessage.setReplyTo(new Address[]{new InternetAddress("replyto@example.com")});
    mimeMessage.setSubject("件名", encoding);

    mimeMessage.setText("本文", encoding);
    mimeMessage.setHeader("Content-Transfer-Encoding", "7bit");
    mimeMessage.setHeader("Content-Type", STR."text/plain; charset=\{encoding}");

    Transport.send(mimeMessage);
}

private static Properties getProperties() {
    Properties props = new Properties();
    props.setProperty("mail.smtp.host", "smtp.gmail.com");
// SMTPS
//    props.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
//    props.setProperty("mail.smtp.socketFactory.fallback", "false");
//    props.setProperty("mail.smtp.port", "465");

// STARTTLS
    props.setProperty("mail.smtp.port", "587");
    props.put("mail.smtp.starttls.enable", "true");

    props.setProperty("mail.smtp.auth", "true");
    props.setProperty("mail.smtp.ssl.checkserveridentity", "true");
    props.setProperty("mail.debug", "true");
    return props;
}

587? 464?

getPropertiesで設定している587はSTARTTLSという、セッション確立後にTLSを確立して安全性を確保する仕組みで使われるポート番号です。465番はセッション確立当初からTLS接続を行う場合に使います。
TLSの意義は暗号化することで盗聴から守ることに加えて、証明書を検証することで中間者/MITM攻撃を防ぐことにあります。つまり通信する相手が成りすましをしていないことを確認できます。
証明書の有効期限の切れたWebサイトや、ドメイン名を変えたのに別のドメインの証明書を使っているサイトなどにアクセスすると警告が出るのはそのせいです。

JavaMailはデフォルトでホスト名の検証を行わない

JavaMailは歴史のあるAPIで、SSLのサポートは後から追加されました。互換性維持のため、デフォルトではホスト名の検証を行わないそうです。
Notes for use of SSL with JavaMail
なので、SMTPS/STARTTLSで接続する際は先のコード例のように
props.setProperty(“mail.smtp.ssl.checkserveridentity”, “true”);
と、”mail.smtp.ssl.checkserveridentity”プロパティを設定する必要があります。
設定しなくてもメールは送信できるし、サーバサイドで使われることの多いJavaアプリケーションにたいして中間者攻撃を実施できるケースは少ないです。が、可能性が低いこと=ホスト名検証をしなくて良いこと、にはなりません。