/* * 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.protobuf.ByteString; 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.Config; import org.terasology.registry.CoreRegistry; import org.terasology.identity.BadEncryptedDataException; import org.terasology.identity.CertificateGenerator; import org.terasology.identity.CertificatePair; import org.terasology.identity.IdentityConstants; 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.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; /** * Authentication handler for the server end of the handshake */ public class ServerHandshakeHandler extends SimpleChannelUpstreamHandler { private static final Logger logger = LoggerFactory.getLogger(ServerHandshakeHandler.class); private Config config = CoreRegistry.get(Config.class); private ServerConnectionHandler serverConnectionHandler; private byte[] serverRandom = new byte[IdentityConstants.SERVER_CLIENT_RANDOM_LENGTH]; private NetData.HandshakeHello serverHello; @Override public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { super.channelOpen(ctx, e); serverConnectionHandler = ctx.getPipeline().get(ServerConnectionHandler.class); } @Override public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { logger.info("Sending Server Hello"); PublicIdentityCertificate serverPublicCert = config.getSecurity().getServerPublicCertificate(); new SecureRandom().nextBytes(serverRandom); serverHello = NetData.HandshakeHello.newBuilder() .setRandom(ByteString.copyFrom(serverRandom)) .setCertificate(NetMessageUtil.convert(serverPublicCert)) .setTimestamp(System.currentTimeMillis()) .build(); e.getChannel().write(NetData.NetMessage.newBuilder() .setHandshakeHello(serverHello) .build()); } @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) { NetData.NetMessage message = (NetData.NetMessage) e.getMessage(); if (message.hasNewIdentityRequest()) { processNewIdentityRequest(message.getNewIdentityRequest(), ctx); } else if (message.hasHandshakeHello() && message.hasHandshakeVerification()) { processClientHandshake(message.getHandshakeHello(), message.getHandshakeVerification(), ctx); } } private void processClientHandshake(NetData.HandshakeHello clientHello, NetData.HandshakeVerification handshakeVerification, ChannelHandlerContext ctx) { logger.info("Received client certificate"); PublicIdentityCertificate clientCert = NetMessageUtil.convert(clientHello.getCertificate()); if (!clientCert.verifySignedBy(config.getSecurity().getServerPublicCertificate())) { logger.error("Received invalid client certificate, ending connection attempt"); ctx.getChannel().close(); return; } byte[] clientSignature = handshakeVerification.getSignature().toByteArray(); byte[] signatureData = HandshakeCommon.getSignatureData(serverHello, clientHello); if (!clientCert.verify(signatureData, clientSignature)) { logger.error("Received invalid verification signature, ending connection attempt"); ctx.getChannel().close(); return; } logger.info("Sending server verification"); byte[] serverSignature = config.getSecurity().getServerPrivateCertificate().sign(signatureData); ctx.getChannel().write(NetData.NetMessage.newBuilder() .setHandshakeVerification(NetData.HandshakeVerification.newBuilder() .setSignature(ByteString.copyFrom(serverSignature))).build()); // Identity has been established, inform the server handler and withdraw from the pipeline ctx.getPipeline().remove(this); serverConnectionHandler.channelAuthenticated(clientCert); } private void processNewIdentityRequest(NetData.NewIdentityRequest newIdentityRequest, ChannelHandlerContext ctx) { logger.info("Received new identity request"); try { byte[] preMasterSecret = config.getSecurity().getServerPrivateCertificate().decrypt(newIdentityRequest.getPreMasterSecret().toByteArray()); byte[] masterSecret = HandshakeCommon.generateMasterSecret(preMasterSecret, newIdentityRequest.getRandom().toByteArray(), serverRandom); // Generate a certificate pair for the client CertificatePair clientCertificates = new CertificateGenerator().generate(config.getSecurity().getServerPrivateCertificate()); NetData.CertificateSet certificateData = NetData.CertificateSet.newBuilder() .setPublicCertificate(NetMessageUtil.convert(clientCertificates.getPublicCert())) .setPrivateExponent(ByteString.copyFrom(clientCertificates.getPrivateCert().getExponent().toByteArray())) .build(); byte[] encryptedCert = null; try { SecretKeySpec key = HandshakeCommon.generateSymmetricKey(masterSecret, newIdentityRequest.getRandom().toByteArray(), serverRandom); Cipher cipher = Cipher.getInstance(IdentityConstants.SYMMETRIC_ENCRYPTION_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, key); encryptedCert = cipher.doFinal(certificateData.toByteArray()); } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) { logger.error("Unexpected error encrypting certificate for sending, ending connection attempt", e); ctx.getChannel().close(); return; } ctx.getChannel().write(NetData.NetMessage.newBuilder() .setProvisionIdentity(NetData.ProvisionIdentity.newBuilder() .setEncryptedCertificates(ByteString.copyFrom(encryptedCert))) .build()); // Identity has been established, inform the server handler and withdraw from the pipeline ctx.getPipeline().remove(this); serverConnectionHandler.channelAuthenticated(clientCertificates.getPublicCert()); } catch (BadEncryptedDataException e) { logger.error("Received invalid encrypted pre-master secret, ending connection attempt"); ctx.getChannel().close(); } } }