Unrecognized SSL message, plaintext connection?

  以这个错误信息为文章标题是不是更醒目一点,这是JavaMail使用SSL的方式登录邮箱时抛出的异常。代码如:

public class JavaMailTest1 {
	public static void main(String[] args) throws MessagingException {
		Properties props = new Properties();
		props.setProperty("mail.debug", "true");
		props.setProperty("mail.smtp.auth", "true");
		props.setProperty("mail.transport.protocol", "smtp");
		
		// SSL
		props.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
		props.setProperty("mail.smtp.socketFactory.fallback", "false");
		props.setProperty("mail.smtp.port", "465");
		props.setProperty("mail.smtp.socketFactory.port", "465");
		
		Session session = Session.getInstance(props);
		
		Message msg = new MimeMessage(session);
		msg.setText("你好吗?");
		msg.setFrom(new InternetAddress("发件箱地址"));
		
		Transport transport = session.getTransport();
		transport.connect("smtp.sina.com", "用户名", "密码");
		transport.sendMessage(msg, new Address[] {new InternetAddress("收件箱地址")});
		transport.close();
	}
}

       解决该问题方式:将端口号修改成465。通常服务器的SSL端口是443,邮箱服务器中的是465。修改后,运行程序,又会出现一个新的异常:

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at com.sun.mail.smtp.SMTPTransport.openServer(SMTPTransport.java:1972)
	at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:642)
	at javax.mail.Service.connect(Service.java:295)
	at javax.mail.Service.connect(Service.java:176)

       这通常意味着是该服务器使用的是测试证书(使用密钥工具可能产生的),而不是从一个著名的商业证书颁发机构如Verisign或GoDaddy的证书。 在此情况下,但由于JSSE(Java(TM)SecureSocketExtension)不能假定一个交互式的用户存在,它只是在默认情况下会抛出异常。解决该问题的思路就是将你要连接的SSL服务器的证书添加为JSSE受信任的证书。那如何产生证书呢?通过下面的代码(如果没有看到请刷新页面):

/*
 * Copyright 2006 Sun Microsystems, Inc.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Sun Microsystems nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/**
 * http://blogs.sun.com/andreas/resource/InstallCert.java
 * Use:
 * java InstallCert hostname
 * Example:
 *% java InstallCert ecc.fedora.redhat.com
 */

import javax.net.ssl.*;
import java.io.*;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

/**
 * Class used to add the server's certificate to the KeyStore
 * with your trusted certificates.
 */
public class InstallCert {

    public static void main(String[] args) throws Exception {
        String host;
        int port;
        char[] passphrase;
        if ((args.length == 1) || (args.length == 2)) {
            String[] c = args[0].split(":");
            host = c[0];
            port = (c.length == 1) ? 443 : Integer.parseInt(c[1]);
            String p = (args.length == 1) ? "changeit" : args[1];
            passphrase = p.toCharArray();
        } else {
            System.out.println("Usage: java InstallCert [:port] [passphrase]");
            return;
        }

        File file = new File("jssecacerts");
        if (file.isFile() == false) {
            char SEP = File.separatorChar;
            File dir = new File(System.getProperty("java.home") + SEP
                    + "lib" + SEP + "security");
            file = new File(dir, "jssecacerts");
            if (file.isFile() == false) {
                file = new File(dir, "cacerts");
            }
        }
        System.out.println("Loading KeyStore " + file + "...");
        InputStream in = new FileInputStream(file);
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(in, passphrase);
        in.close();

        SSLContext context = SSLContext.getInstance("TLS");
        TrustManagerFactory tmf =
                TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ks);
        X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
        SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
        context.init(null, new TrustManager[]{tm}, null);
        SSLSocketFactory factory = context.getSocketFactory();

        System.out.println("Opening connection to " + host + ":" + port + "...");
        SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
        socket.setSoTimeout(10000);
        try {
            System.out.println("Starting SSL handshake...");
            socket.startHandshake();
            socket.close();
            System.out.println();
            System.out.println("No errors, certificate is already trusted");
        } catch (SSLException e) {
            System.out.println();
            e.printStackTrace(System.out);
        }

        X509Certificate[] chain = tm.chain;
        if (chain == null) {
            System.out.println("Could not obtain server certificate chain");
            return;
        }

        BufferedReader reader =
                new BufferedReader(new InputStreamReader(System.in));

        System.out.println();
        System.out.println("Server sent " + chain.length + " certificate(s):");
        System.out.println();
        MessageDigest sha1 = MessageDigest.getInstance("SHA1");
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        for (int i = 0; i < chain.length; i++) {
            X509Certificate cert = chain[i];
            System.out.println
                    (" " + (i + 1) + " Subject " + cert.getSubjectDN());
            System.out.println("   Issuer  " + cert.getIssuerDN());
            sha1.update(cert.getEncoded());
            System.out.println("   sha1    " + toHexString(sha1.digest()));
            md5.update(cert.getEncoded());
            System.out.println("   md5     " + toHexString(md5.digest()));
            System.out.println();
        }

        System.out.println("Enter certificate to add to trusted keystore or 'q' to quit: [1]");
        String line = reader.readLine().trim();
        int k;
        try {
            k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1;
        } catch (NumberFormatException e) {
            System.out.println("KeyStore not changed");
            return;
        }

        X509Certificate cert = chain[k];
        String alias = host + "-" + (k + 1);
        ks.setCertificateEntry(alias, cert);

        OutputStream out = new FileOutputStream("jssecacerts");
        ks.store(out, passphrase);
        out.close();

        System.out.println();
        System.out.println(cert);
        System.out.println();
        System.out.println
                ("Added certificate to keystore 'jssecacerts' using alias '"
                        + alias + "'");
    }

    private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();

    private static String toHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 3);
        for (int b : bytes) {
            b &= 0xff;
            sb.append(HEXDIGITS[b >> 4]);
            sb.append(HEXDIGITS[b & 15]);
            sb.append(' ');
        }
        return sb.toString();
    }

    private static class SavingTrustManager implements X509TrustManager {

        private final X509TrustManager tm;
        private X509Certificate[] chain;

        SavingTrustManager(X509TrustManager tm) {
            this.tm = tm;
        }

        public X509Certificate[] getAcceptedIssuers() {
            throw new UnsupportedOperationException();
        }

        public void checkClientTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
            throw new UnsupportedOperationException();
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
            this.chain = chain;
            tm.checkServerTrusted(chain, authType);
        }
    }

}

        编译该类并运行,运行时要传一个参数:SSL服务器域名:端口号,如:java InstallCert smtp.sina.com:465,不输端口的话默认为443。运行之后:

Loading KeyStore C:\Program Files\Java\jre7\lib\security\cacerts...
Opening connection to smtp.sina.com:465...
Starting SSL handshake...

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.ssl.Alerts.getSSLException(Unknown Source)
        at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source)
        at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
        at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
        at sun.security.ssl.ClientHandshaker.serverCertificate(Unknown Source)
        at sun.security.ssl.ClientHandshaker.processMessage(Unknown Source)
        at sun.security.ssl.Handshaker.processLoop(Unknown Source)
        at sun.security.ssl.Handshaker.process_record(Unknown Source)
        at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
        at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
        at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
        at InstallCert.main(InstallCert.java:97)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.validator.PKIXValidator.doBuild(Unknown Source)
        at sun.security.validator.PKIXValidator.engineValidate(Unknown Source)
        at sun.security.validator.Validator.validate(Unknown Source)
        at sun.security.ssl.X509TrustManagerImpl.validate(Unknown Source)
        at sun.security.ssl.X509TrustManagerImpl.checkTrusted(Unknown Source)
        at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(Unknown Source)
        at InstallCert$SavingTrustManager.checkServerTrusted(InstallCert.java:192)
        at sun.security.ssl.AbstractTrustManagerWrapper.checkServerTrusted(Unknown Source)
        ... 9 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(Unknown Source)
        at java.security.cert.CertPathBuilder.build(Unknown Source)
        ... 17 more

Server sent 1 certificate(s):

 1 Subject CN=*.sina.com, O="Sina.com Technology(China)Co.,ltd", L=Beijing, ST=Beijing, C=CN, SERIALNUMBER=mL/iTnzl-0Pr1rH-6U2RZH/h3zFjZxoK
   Issuer  CN=GeoTrust SSL CA, O="GeoTrust, Inc.", C=US
   sha1    43 a9 5b bc 9b 86 85 99 e3 21 63 af b0 09 78 4a 67 25 d7 a1
   md5     90 c2 45 da 67 68 cd c2 44 56 21 ef ed c6 6b 5e

Enter certificate to add to trusted keystore or 'q' to quit: [1]

       抛出了与我们程序中同样的错误,这里我们输入1,回车。它又输出了一堆信息,这里不粘了,这时证书已经生成了,在哪呢?在InstallCert.java所在的目录中,有一个名为jssecacerts的文件,这就是我们要的证书。将其放到我们程序所在的JSSE中,$JAVA_HOME/jre/lib/security。这时,再次运行我们的发邮件程序,发送成功。

更新2014-01-04

       今天无意中看到了JavaMail有这样的协议支持描述:

Protocol	Store or	Uses	Supports
Name		Transport?	SSL?	STARTTLS?
-------------------------------------------------
imap		Store		No	    Yes
imaps		Store		Yes	    N/A
pop3		Store		No	    Yes
pop3s		Store		Yes	    N/A
smtp		Transport	No	    Yes
smtps		Transport	Yes	    N/A

       Transport使用SSL连接邮箱协议名称需要使用smtps,而不是smtp,那前面提到的程序只需这样:

public class JavaMailTest1 {
	public static void main(String[] args) throws MessagingException {
		Properties props = new Properties();
		props.setProperty("mail.debug", "true");
		props.setProperty("mail.smtp.auth", "true");
		
		// 协议名称设置为smtps,会使用SSL
		props.setProperty("mail.transport.protocol", "smtps");
		
		Session session = Session.getInstance(props);
		
		Message msg = new MimeMessage(session);
		msg.setText("你好吗?");
		msg.setFrom(new InternetAddress("发件箱地址"));
		
		Transport transport = session.getTransport();
		transport.connect("smtp.sina.com", "用户名", "密码");
		transport.sendMessage(msg, new Address[] {new InternetAddress("收件箱地址")});
		transport.close();
	}
}

       这样,JavaMail会自动使用SSL,并且使用465端口。

       参考:https://java.net/projects/javamail/pages/InstallCerthttp://infposs.blogspot.com/2013/06/installcert-and-java-7.html

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏数据和云

诊断案例:从实例挂起到归档失败和内存管理的蝴蝶效应

杨廷琨(yangtingkun) 云和恩墨 CTO 高级咨询顾问,Oracle ACE 总监,ITPUB Oracle 数据库管理版版主 编辑手记:在很多数据...

2769
来自专栏开心的学习之路

03 Jme3和Nifty1.4.2中文显示

用JMonkey最大的问题就是中文问题,IDE不是中文没关系,反正可以迁移到Idea里,但是打包发布的项目以及Nifty做的GUI里没有中文就心塞塞了。好在找到...

33710
来自专栏Hongten

java开发_系统托盘

========================================================================

592
来自专栏Hongten

spring开发_JDBC操作MySQL数据库_使用xml配置事务管理

http://www.cnblogs.com/hongten/archive/2012/03/09/java_spring_jdbc.html

551
来自专栏10km的专栏

windows下msvc/mingw静态编译 lmdb的CMakeLists.txt

LMDB的全称是Lightning Memory-Mapped Database,闪电般的内存映射数据库,在github可以找到源码 https://git...

2596
来自专栏沃趣科技

Oracle 12c系列(六)|Relocate a PDB

Relocating a PDB是Oracle在12C中推出的一种新的数据迁移方式,在采用Relocate时可以使用最短的停机时间在不同的CDB直接迁移PDB。

3487
来自专栏杨建荣的学习笔记

dataguard中MRP无法启动的问题分析和解决(r5笔记第82天)

自己手头有一套dataguard环境,因为也有些日子没有用了,结果突然心血来潮准备启动起来学习一下,突然发现在敲了命令 recover managed stan...

2598
来自专栏杨建荣的学习笔记

巧用shell脚本分析数据库用户(r2第4天)

在数据库维护中,可能对于一个陌生的schema,需要了解它的一些情况,可以使用如下的脚本来很快得到一个报告,里面包含了详尽的信息。 用户占用的空间,权限,角色和...

3217
来自专栏杨建荣的学习笔记

备库报警邮件的分析案例(三)(r7笔记第16天)

继前两篇分析了一个看似非常普通的报警邮件,结果在分析问题的时候八面玲珑,相关因素都给分析了一下,没想到还真是有不小的收获。 前两篇地址: 备库报警邮件的分析案例...

3115
来自专栏乐沙弥的世界

禁用与卸载Oracle AWR特性

    AWR需要禁用?这么好的东东。缺省的情况下,AWR是可以使用的,需要耗用一定的sysaux表空间。但涉及到有关AWR相关的调试包(需要license)会...

622

扫码关注云+社区