Package org.terasology.network.internal

Source Code of org.terasology.network.internal.ClientHandshakeHandler

/*
* Copyright 2013 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.network.internal;

import com.google.common.primitives.Bytes;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.config.ClientIdentity;
import org.terasology.config.Config;
import org.terasology.registry.CoreRegistry;
import org.terasology.identity.IdentityConstants;
import org.terasology.identity.PrivateIdentityCertificate;
import org.terasology.identity.PublicIdentityCertificate;
import org.terasology.protobuf.NetData;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

/**
* Authentication handler for the client end of the authentication handshake.
*/
public class ClientHandshakeHandler extends SimpleChannelUpstreamHandler {

    private static final Logger logger = LoggerFactory.getLogger(ClientHandshakeHandler.class);
    private static final String AUTHENTICATION_FAILURE = "Authentication failure";

    private Config config = CoreRegistry.get(Config.class);
    private ClientConnectionHandler clientConnectionHandler;
    private JoinStatusImpl joinStatus;

    private byte[] serverRandom;
    private byte[] clientRandom;
    private byte[] masterSecret;
    private NetData.HandshakeHello serverHello;
    private NetData.HandshakeHello clientHello;

    private boolean requestedCertificate;
    private ClientIdentity identity;
    private PublicIdentityCertificate serverCertificate;

    public ClientHandshakeHandler(JoinStatusImpl joinStatus) {
        this.joinStatus = joinStatus;
    }

    @Override
    public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
        super.channelOpen(ctx, e);
        clientConnectionHandler = ctx.getPipeline().get(ClientConnectionHandler.class);
        joinStatus.setCurrentActivity("Authenticating with server");
    }

    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
        NetData.NetMessage message = (NetData.NetMessage) e.getMessage();
        if (message.hasHandshakeHello()) {
            processServerHello(message.getHandshakeHello(), ctx);
        } else if (message.hasProvisionIdentity()) {
            processNewIdentity(message.getProvisionIdentity(), ctx);
        } else if (message.hasHandshakeVerification()) {
            processHandshakeVerification(message.getHandshakeVerification(), ctx);
        }
    }

    private void processHandshakeVerification(NetData.HandshakeVerification handshakeVerification, ChannelHandlerContext ctx) {
        logger.info("Received server verification");
        if (serverHello == null || clientHello == null) {
            logger.error("Received server verification without requesting it: cancelling authentication");
            joinStatus.setErrorMessage(AUTHENTICATION_FAILURE);
            ctx.getChannel().close();
            return;
        }

        if (!serverCertificate.verify(HandshakeCommon.getSignatureData(serverHello, clientHello), handshakeVerification.getSignature().toByteArray())) {
            logger.error("Server failed verification: cancelling authentication");
            joinStatus.setErrorMessage(AUTHENTICATION_FAILURE);
            ctx.getChannel().close();
            return;
        }

        // And we're authenticated.
        ctx.getPipeline().remove(this);
        clientConnectionHandler.channelAuthenticated(ctx);
    }

    private void processNewIdentity(NetData.ProvisionIdentity provisionIdentity, ChannelHandlerContext ctx) {
        logger.info("Received identity from server");
        if (!requestedCertificate) {
            logger.error("Received identity without requesting it: cancelling authentication");
            joinStatus.setErrorMessage(AUTHENTICATION_FAILURE);
            ctx.getChannel().close();
            return;
        }

        try {
            byte[] decryptedCert = null;
            try {
                SecretKeySpec key = HandshakeCommon.generateSymmetricKey(masterSecret, clientRandom, serverRandom);
                Cipher cipher = Cipher.getInstance(IdentityConstants.SYMMETRIC_ENCRYPTION_ALGORITHM);
                cipher.init(Cipher.DECRYPT_MODE, key);
                decryptedCert = cipher.doFinal(provisionIdentity.getEncryptedCertificates().toByteArray());
            } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
                logger.error("Unexpected error decrypting received certificate, ending connection attempt", e);
                joinStatus.setErrorMessage(AUTHENTICATION_FAILURE);
                ctx.getChannel().close();
                return;
            }

            NetData.CertificateSet certificateSet = NetData.CertificateSet.parseFrom(decryptedCert);
            NetData.Certificate publicCertData = certificateSet.getPublicCertificate();

            PublicIdentityCertificate publicCert = NetMessageUtil.convert(publicCertData);

            if (!publicCert.verifySignedBy(serverCertificate)) {
                logger.error("Received invalid certificate, not signed by server: cancelling authentication");
                joinStatus.setErrorMessage(AUTHENTICATION_FAILURE);
                ctx.getChannel().close();
                return;
            }

            BigInteger exponent = new BigInteger(certificateSet.getPrivateExponent().toByteArray());
            PrivateIdentityCertificate privateCert = new PrivateIdentityCertificate(publicCert.getModulus(), exponent);

            // Store identity for later use
            identity = new ClientIdentity(publicCert, privateCert);
            config.getSecurity().addIdentity(serverCertificate, identity);
            config.save();

            // And we're authenticated.
            ctx.getPipeline().remove(this);
            clientConnectionHandler.channelAuthenticated(ctx);
        } catch (InvalidProtocolBufferException e) {
            logger.error("Received invalid certificate data: cancelling authentication", e);
            joinStatus.setErrorMessage(AUTHENTICATION_FAILURE);
            ctx.getChannel().close();
        }
    }

    private void processServerHello(NetData.HandshakeHello helloMessage, ChannelHandlerContext ctx) {
        if (serverHello == null) {
            logger.info("Received Server Hello");
            serverHello = helloMessage;
            serverRandom = helloMessage.getRandom().toByteArray();
            NetData.Certificate cert = helloMessage.getCertificate();
            serverCertificate = NetMessageUtil.convert(cert);

            if (!serverCertificate.verifySelfSigned()) {
                logger.error("Received invalid server certificate: cancelling authentication");
                joinStatus.setErrorMessage(AUTHENTICATION_FAILURE);
                ctx.getChannel().close();
                return;
            }

            clientRandom = new byte[IdentityConstants.SERVER_CLIENT_RANDOM_LENGTH];

            identity = config.getSecurity().getIdentity(serverCertificate);
            if (identity == null) {
                requestIdentity(ctx);
            } else {
                sendCertificate(helloMessage, ctx);
            }

        } else {
            logger.error("Received multiple hello messages from server: cancelling authentication");
            joinStatus.setErrorMessage(AUTHENTICATION_FAILURE);
            ctx.getChannel().close();
        }

    }

    private void sendCertificate(NetData.HandshakeHello helloMessage, ChannelHandlerContext ctx) {
        logger.info("Sending client certificate");
        PublicIdentityCertificate pubClientCert = identity.getPlayerPublicCertificate();

        clientHello = NetData.HandshakeHello.newBuilder()
                .setRandom(ByteString.copyFrom(clientRandom))
                .setCertificate(NetMessageUtil.convert(pubClientCert))
                .setTimestamp(System.currentTimeMillis())
                .build();

        byte[] dataToSign = Bytes.concat(helloMessage.toByteArray(), clientHello.toByteArray());
        byte[] signature = identity.getPlayerPrivateCertificate().sign(dataToSign);

        ctx.getChannel().write(NetData.NetMessage.newBuilder()
                .setHandshakeHello(clientHello)
                .setHandshakeVerification(NetData.HandshakeVerification.newBuilder()
                        .setSignature(ByteString.copyFrom(signature)))
                .build());
    }

    private void requestIdentity(ChannelHandlerContext ctx) {
        logger.info("No existing identity, requesting one");

        byte[] preMasterSecret = new byte[IdentityConstants.PREMASTER_SECRET_LENGTH];
        new SecureRandom().nextBytes(preMasterSecret);
        byte[] encryptedPreMasterSecret = serverCertificate.encrypt(preMasterSecret);

        masterSecret = HandshakeCommon.generateMasterSecret(preMasterSecret, clientRandom, serverRandom);

        ctx.getChannel().write(NetData.NetMessage.newBuilder()
                .setNewIdentityRequest(NetData.NewIdentityRequest.newBuilder()
                        .setPreMasterSecret(ByteString.copyFrom(encryptedPreMasterSecret))
                        .setRandom(ByteString.copyFrom(clientRandom)))
                .build());
        requestedCertificate = true;
    }


}
TOP

Related Classes of org.terasology.network.internal.ClientHandshakeHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.