ECDH服务器密钥交换消息上的签名无效

时间:2017-02-24 17:47:25

标签: java ssl netty

我正在尝试使用本地证书颁发机构,并且我一直收到“ECDH服务器密钥交换消息上的无效签名”错误。 对这个问题的复杂性提前道歉。 您可以通过以下方式从github获取完整的资源:

git clone git://github.com/ClarkHobbie/ssltest.git

然后用

编译它
mvn package

然后用

运行它
java -cp target\ssl-test-1.0-SNAPSHOT.jar;lib\netty-all-4.1.6.Final.jar SSLTest server

并在另一个窗口中使用

java -cp target\ssl-test-1.0-SNAPSHOT.jar;lib\netty-all-4.1.6.Final.jar SSLTest client

当我尝试运行程序时,我会看到提示符(例如“localhost:6789>”),我会尝试类似“test”的内容然后我收到错误。

如果我不使用netty all(请参阅第二段代码),它似乎有效。

这是完整的堆栈跟踪:

io.netty.handler.codec.DecoderException: javax.net.ssl.SSLKeyException: Invalid signature on ECDH server key exchange message
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:442)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:248)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:373)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:351)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1334)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:373)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:926)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:129)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:651)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:574)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:488)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:450)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:873)
    at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:144)
    at java.lang.Thread.run(Thread.java:745)
Caused by: javax.net.ssl.SSLKeyException: Invalid signature on ECDH server key exchange message
    at sun.security.ssl.Handshaker.checkThrown(Handshaker.java:1434)
    at sun.security.ssl.SSLEngineImpl.checkTaskThrown(SSLEngineImpl.java:535)
    at sun.security.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:813)
    at sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:781)
    at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:624)
    at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1097)
    at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:968)
    at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:902)
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:411)
    ... 16 more
Caused by: javax.net.ssl.SSLKeyException: Invalid signature on ECDH server key exchange message
    at sun.security.ssl.HandshakeMessage$ECDH_ServerKeyExchange.<init>(HandshakeMessage.java:1119)
    at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:284)
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:979)
    at sun.security.ssl.Handshaker$1.run(Handshaker.java:919)
    at sun.security.ssl.Handshaker$1.run(Handshaker.java:916)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.security.ssl.Handshaker$DelegatedTask.run(Handshaker.java:1369)
    at io.netty.handler.ssl.SslHandler.runDelegatedTasks(SslHandler.java:1123)
    at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1008)
    ... 18 more

这是netty版本(抛出异常):

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslHandler;

import javax.net.ssl.TrustManagerFactory;
import java.io.*;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;

/**
 * Created by Clark on 2/27/2017.
 */
public class SSLTest {
    public static class ServerChannelInitializer extends ChannelInitializer<NioSocketChannel> {
        private SslContext sslContext;

        public ServerChannelInitializer (SslContext sslContext) {
            this.sslContext = sslContext;
        }

        public void initChannel (NioSocketChannel serverSocketChannel) {
            if (null != sslContext) {
                SslHandler sslHandler = sslContext.newHandler(serverSocketChannel.alloc());
                serverSocketChannel.pipeline().addLast(sslHandler);
            }

            EchoHandler echoHandler = new EchoHandler();
            serverSocketChannel.pipeline().addLast(echoHandler);
        }
    }

    public static class UserInput {
        private static UserInput ourInstance;

        private String prompt;
        private BufferedReader bufferedReader;

        public static synchronized void initializeClass (String prompt) {
            if (null == ourInstance) {
                ourInstance = new UserInput (prompt);
            }
        }

        public static UserInput getInstance () {
            return ourInstance;
        }

        private UserInput (String prompt) {
            this.prompt = prompt;

            InputStreamReader inputStreamReader = new InputStreamReader(System.in);
            this.bufferedReader = new BufferedReader(inputStreamReader);
        }

        public String getLine () throws IOException {
            System.out.print (prompt);
            return bufferedReader.readLine();
        }
    }

    public static class ClientInitializer extends ChannelInitializer<SocketChannel> {
        private SslContext sslContext;

        public ClientInitializer (SslContext sslContext) {
            this.sslContext = sslContext;
        }

        public void initChannel (SocketChannel socketChannel) {
            if (null != sslContext) {
                SslHandler sslHandler = sslContext.newHandler(socketChannel.alloc());
                socketChannel.pipeline().addLast(sslHandler);
            }

            ClientChannelHandler clientChannelHandler = new ClientChannelHandler();
            socketChannel.pipeline().addLast(clientChannelHandler);
        }
    }

    public static class ClientChannelHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            String input = UserInput.getInstance().getLine();

            ByteBuf byteBuf = Unpooled.directBuffer(256);
            ByteBufUtil.writeUtf8(byteBuf, input);
            ctx.writeAndFlush(byteBuf);
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf byteBuf = (ByteBuf) msg;
            byte[] buffer = new byte[byteBuf.readableBytes()];
            byteBuf.getBytes(0, buffer);
            String s = new String(buffer);

            System.out.println (s);

            s = UserInput.getInstance().getLine();
            if (s.equalsIgnoreCase("quit") || s.equalsIgnoreCase("bye")) {
                System.out.println ("quiting");
                System.exit(0);
            }

            byteBuf = Unpooled.directBuffer(256);
            ByteBufUtil.writeUtf8(byteBuf, s);
            ctx.writeAndFlush(byteBuf);
        }

        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }

    public static class EchoHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf byteBuf = (ByteBuf) msg;
            byte[] buffer = new byte[byteBuf.readableBytes()];
            byteBuf.getBytes(0, buffer);
            String s = new String(buffer);

            System.out.println("got " + s);

            ctx.writeAndFlush(msg);
        }
    }

    public static class CommandLine {
        private String[] argv;
        private int argIndex = 0;
        private String mode = "server";
        private boolean useTls = true;
        private String host = "localhost";
        private int port = 6789;

        public String[] getArgv () {
            return argv;
        }

        public String getArg () {
            if (argIndex >= argv.length)
                return null;

            return argv[argIndex];
        }

        public void advance () {
            argIndex++;
        }

        public String getMode () {
            return mode;
        }

        public void setMode (String mode) {
            this.mode = mode;
        }

        public boolean useTls () {
            return useTls;
        }

        public void setUseTls (boolean useTls) {
            this.useTls = useTls;
        }

        public String getHost () {
            return host;
        }

        public void setHost (String host) {
            this.host = host;
        }

        public int getPort () {
            return port;
        }

        public void setPort (int port) {
            this.port = port;
        }

        public CommandLine (String[] argv) {
            this.argv = argv;
            parse();
        }

        public void parse () {
            if (argv.length < 1)
                return;

            if (null != getArg() && getArg().equalsIgnoreCase("nossl")) {
                System.out.println ("Plaintext mode");
                setUseTls(false);
                advance();
            }

            if (null != getArg()) {
                setMode(getArg());
                advance();
            }

            if (null != getArg()) {
                setHost(getArg());
                advance();
            }

            if (null != getArg()) {
                int temp = Integer.parseInt(getArg());
                setPort(temp);
                advance();
            }
        }
    }

    private CommandLine commandLine;

    public CommandLine getCommandLine() {
        return commandLine;
    }

    public SSLTest (CommandLine commandLine) {
        this.commandLine = commandLine;
    }

    public static void closeIgnoreExceptions (Reader reader)
    {
        if (null != reader) {
            try {
                reader.close();
            } catch (IOException e) {}
        }
    }

    public static void closeIgnoreExceptions (InputStream inputStream) {
        if (null != inputStream) {
            try {
                inputStream.close();
            } catch (IOException e) {}
        }
    }

    public static void closeIfNonNull (PrintWriter printWriter) {
        if (null != printWriter) {
            printWriter.close();
        }
    }

    public static KeyStore getKeyStore (String filename, String password) {
        KeyStore keyStore = null;
        FileInputStream fileInputStream = null;

        try {
            fileInputStream = new FileInputStream(filename);
            keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load (fileInputStream, password.toCharArray());
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        } finally {
            closeIgnoreExceptions(fileInputStream);
        }

        return keyStore;
    }

    public static PrivateKey getPrivateKey (String filename, String password, String alias) {
        PrivateKey privateKey = null;
        FileInputStream fileInputStream = null;

        try {
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            fileInputStream = new FileInputStream(filename);
            keyStore.load(fileInputStream, password.toCharArray());
            privateKey = (PrivateKey) keyStore.getKey(alias, password.toCharArray());
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }

        return privateKey;
    }


    public void server () {
        try {
            SslContext sslContext = null;

            if (getCommandLine().useTls()) {
                String trustStoreFilename = "truststore";
                String trustStorePassword = "whatever";
                String trustStoreAlias = "ca";

                String keyStoreFilename = "serverkeystore";
                String keyStorePassword = "whatever";
                String keyStoreAlias = "server";

                X509Certificate certificate = getCertificate(trustStoreFilename, trustStorePassword, trustStoreAlias);
                PrivateKey privateKey = getPrivateKey(keyStoreFilename, keyStorePassword, keyStoreAlias);
                sslContext = SslContextBuilder
                        .forServer(privateKey, certificate)
                        .build();
            }

            ServerChannelInitializer serverChannelInitializer = new ServerChannelInitializer(sslContext);

            NioEventLoopGroup workerGroup = new NioEventLoopGroup();
            NioEventLoopGroup bossGroup = new NioEventLoopGroup();

            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.childHandler(serverChannelInitializer);
            serverBootstrap.group(bossGroup, workerGroup);
            serverBootstrap.channel(NioServerSocketChannel.class);

            System.out.println ("listening on port " + getCommandLine().getPort());

            serverBootstrap.bind(getCommandLine().getPort()).sync();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    public static X509Certificate getCertificate (String filename, String password, String alias) {
        KeyStore keyStore = null;
        FileInputStream fileInputStream = null;
        Certificate certificate = null;

        try {
            fileInputStream = new FileInputStream(filename);
            keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(fileInputStream, password.toCharArray());
            certificate = keyStore.getCertificate(alias);
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }

        return (X509Certificate) certificate;
    }

    public static TrustManagerFactory getTrustManagerFactory (String filename, String password) {
        TrustManagerFactory trustManagerFactory = null;

        try {
            KeyStore keyStore = getKeyStore(filename, password);
            trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
            System.exit(1);
        }

        return trustManagerFactory;
    }

    public void client () {
        try {
            String trustStoreFilename = "truststore";
            String trustStorePassword = "whatever";

            SslContext sslContext = null;

            if (getCommandLine().useTls()) {
                TrustManagerFactory trustManagerFactory = getTrustManagerFactory(trustStoreFilename, trustStorePassword);
                sslContext = SslContextBuilder
                        .forClient()
                        .trustManager(trustManagerFactory)
                        .build();
            }

            NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup();

            Bootstrap clientBootstrap = new Bootstrap();
            clientBootstrap.channel(NioSocketChannel.class);
            clientBootstrap.group(nioEventLoopGroup);
            clientBootstrap.handler(new ClientInitializer(sslContext));
            clientBootstrap.connect(getCommandLine().getHost(), getCommandLine().getPort());
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    public static void main (String[] argv) {
        CommandLine commandLine = new CommandLine(argv);
        SSLTest sslTest = new SSLTest(commandLine);

        String prompt = commandLine.getHost() + ":" + commandLine.getPort() + "> ";
        UserInput.initializeClass(prompt);

        if (commandLine.getMode().equalsIgnoreCase("server"))
            sslTest.server();
        else if (commandLine.getMode().equalsIgnoreCase("client"))
            sslTest.client();
        else {
            System.err.println ("unknown mode: " + commandLine.getMode());
        }
    }
}

我在Stack Overflow上达到了字符限制(30,000),所以你需要去GitHub看看非netty版本。

https://github.com/ClarkHobbie/ssltest2

至于我为什么要使用加密,以及为什么我特别使用本地证书颁发机构,我正在开发一个位于Web服务前面并为它们记录消息的工具。该工具是群集的,因此任何消息(POST / PUT / DELETE)都可能通过互联网传播。为了识别节点和保护内容,使用加密。客户端可以为其所有节点获取CERT(昂贵,缓慢且不方便)或使用本地证书颁发机构。有关详细信息,请参阅https://ltsllc.blogspot.com/2017/02/the-invalid-signature-problem.html

1 个答案:

答案 0 :(得分:0)

如果您遇到此问题,请使用核心Java和Mina进行尝试。如果您的项目与其中一个合作,请使用其中一个。

我是第一个说这不是理想的解决方案。什么本来好的将是“哦,使用选项xyz它会工作”的行,但经过两个星期的头撞对这个问题,然后在Stack Overflow上一周,然后是一周的工作与Netty的人们一起,我准备使用别的东西了。

我在GitHub(ssltest,ssltest2和ssltest3)上提供了三个测试程序,并注意到虽然该程序不能与Netty一起使用,但它确实适用于核心Java或Mina。最终的结果仍然是“这不是一个错误。”

此时,我的项目似乎与Mina一起工作,所以我将继续这样做。对于其他人:如果你没有遇到这个问题,那么一定要使用Netty。如果您确实遇到了这个问题,那么我建议您尝试使用其他内容,如果可以,请使用它。