diff --git a/src/dorkbox/network/connection/registration/MetaChannel.java b/src/dorkbox/network/connection/registration/MetaChannel.java index 530c3136..28b6ede9 100644 --- a/src/dorkbox/network/connection/registration/MetaChannel.java +++ b/src/dorkbox/network/connection/registration/MetaChannel.java @@ -49,6 +49,7 @@ class MetaChannel { public AsymmetricCipherKeyPair ecdhKey; // used for ECC Diffie-Hellman-Merkle key exchanges: see http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange + // since we are using AES-GCM, the aesIV here **MUST** be exactly 12 bytes public byte[] aesKey; public byte[] aesIV; diff --git a/src/dorkbox/network/connection/registration/remote/RegistrationRemoteHandler.java b/src/dorkbox/network/connection/registration/remote/RegistrationRemoteHandler.java index 0a90f2b8..2b0dcfbc 100644 --- a/src/dorkbox/network/connection/registration/remote/RegistrationRemoteHandler.java +++ b/src/dorkbox/network/connection/registration/remote/RegistrationRemoteHandler.java @@ -15,6 +15,7 @@ */ package dorkbox.network.connection.registration.remote; +import com.esotericsoftware.kryo.io.Input; import dorkbox.network.connection.Connection; import dorkbox.network.connection.ConnectionImpl; import dorkbox.network.connection.EndPoint; @@ -28,6 +29,7 @@ import dorkbox.network.pipeline.udp.KryoEncoderUdpCrypto; import dorkbox.network.util.CryptoSerializationManager; import dorkbox.util.collections.IntMap; import dorkbox.util.collections.IntMap.Entries; +import dorkbox.util.serialization.EccPublicKeySerializer; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; @@ -37,8 +39,11 @@ import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.udt.nio.NioUdtByteConnectorChannel; import io.netty.handler.timeout.IdleStateHandler; +import io.netty.util.ReferenceCountUtil; import org.bouncycastle.crypto.engines.AESFastEngine; import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.slf4j.Logger; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -295,6 +300,59 @@ class RegistrationRemoteHandler extends RegistrationHandle } } + protected final + ECPublicKeyParameters verifyPayload(final Object message, + final Channel channel, + final RegistrationWrapper registrationWrapper, + final Logger logger, + final byte[] payload) { + + if (payload.length == 0) { + logger.error("Invalid decryption of payload. Aborting."); + shutdown(registrationWrapper, channel); + + ReferenceCountUtil.release(message); + return null; + } + + ECPublicKeyParameters ecdhPubKey = EccPublicKeySerializer.read(new Input(payload)); + + if (ecdhPubKey == null) { + logger.error("Invalid decode of ecdh public key. Aborting."); + shutdown(registrationWrapper, channel); + + ReferenceCountUtil.release(message); + return null; + } + return ecdhPubKey; + } + + protected final + boolean verifyAesInfo(final Object message, + final Channel channel, + final RegistrationWrapper registrationWrapper, + final MetaChannel metaChannel, + final Logger logger) { + + if (metaChannel.aesKey.length != 32) { + logger.error("Fatal error trying to use AES key (wrong key length)."); + shutdown(registrationWrapper, channel); + + ReferenceCountUtil.release(message); + return true; + } + // IV length must == 12 because we are using GCM! + else if (metaChannel.aesIV.length != 12) { + logger.error("Fatal error trying to use AES IV (wrong IV length)."); + shutdown(registrationWrapper, channel); + + ReferenceCountUtil.release(message); + return true; + } + + return false; + } + // have to setup AFTER establish connection, data, as we don't want to enable AES until we're ready. @SuppressWarnings("AutoUnboxing") protected final diff --git a/src/dorkbox/network/connection/registration/remote/RegistrationRemoteHandlerClientTCP.java b/src/dorkbox/network/connection/registration/remote/RegistrationRemoteHandlerClientTCP.java index 029d931b..e3b70d7f 100644 --- a/src/dorkbox/network/connection/registration/remote/RegistrationRemoteHandlerClientTCP.java +++ b/src/dorkbox/network/connection/registration/remote/RegistrationRemoteHandlerClientTCP.java @@ -160,7 +160,7 @@ class RegistrationRemoteHandlerClientTCP extends Registrat channel.writeAndFlush(registration); } - @SuppressWarnings({"AutoUnboxing", "AutoBoxing"}) + @SuppressWarnings({"AutoUnboxing", "AutoBoxing", "Duplicates"}) @Override public void channelRead(final ChannelHandlerContext context, final Object message) throws Exception { @@ -252,7 +252,6 @@ class RegistrationRemoteHandlerClientTCP extends Registrat */ byte[] ecdhPubKeyBytes = Arrays.copyOfRange(payload, intLength, payload.length); ECPublicKeyParameters ecdhPubKey = EccPublicKeySerializer.read(new Input(ecdhPubKeyBytes)); - if (ecdhPubKey == null) { logger2.error("Invalid decode of ecdh public key. Aborting."); shutdown(registrationWrapper2, channel); @@ -290,18 +289,13 @@ class RegistrationRemoteHandlerClientTCP extends Registrat sha384.doFinal(digest, 0); metaChannel.aesKey = Arrays.copyOfRange(digest, 0, 32); // 256bit keysize (32 bytes) - metaChannel.aesIV = Arrays.copyOfRange(digest, 32, 48); // 128bit blocksize (16 bytes) + metaChannel.aesIV = Arrays.copyOfRange(digest, 32, 44); // 96bit blocksize (12 bytes) required by AES-GCM // abort if something messed up! - if (metaChannel.aesKey.length != 32) { - logger2.error("Fatal error trying to use AES key (wrong key length)."); - shutdown(registrationWrapper2, channel); - - ReferenceCountUtil.release(message); + if (verifyAesInfo(message, channel, registrationWrapper2, metaChannel, logger2)) { return; } - Registration register = new Registration(); // encrypt the ECDH public key using our previous AES info diff --git a/src/dorkbox/network/connection/registration/remote/RegistrationRemoteHandlerServerTCP.java b/src/dorkbox/network/connection/registration/remote/RegistrationRemoteHandlerServerTCP.java index 5fd1eff3..6f18e67d 100644 --- a/src/dorkbox/network/connection/registration/remote/RegistrationRemoteHandlerServerTCP.java +++ b/src/dorkbox/network/connection/registration/remote/RegistrationRemoteHandlerServerTCP.java @@ -55,7 +55,7 @@ class RegistrationRemoteHandlerServerTCP extends Registrat private static final ECParameterSpec eccSpec = ECNamedCurveTable.getParameterSpec(CryptoECC.p521_curve); private final Object ecdhKeyLock = new Object(); private final ThreadLocal eccEngineLocal = new ThreadLocal(); - private AsymmetricCipherKeyPair ecdhKeyPair = CryptoECC.generateKeyPair(eccSpec, new SecureRandom()); + private AsymmetricCipherKeyPair ecdhKeyPair; private volatile long ecdhTimeout = System.nanoTime(); @@ -81,7 +81,7 @@ class RegistrationRemoteHandlerServerTCP extends Registrat */ private AsymmetricCipherKeyPair getEchdKeyOnRotate(final SecureRandom secureRandom) { - if (System.nanoTime() - this.ecdhTimeout > ECDH_TIMEOUT) { + if (this.ecdhKeyPair == null || System.nanoTime() - this.ecdhTimeout > ECDH_TIMEOUT) { synchronized (this.ecdhKeyLock) { this.ecdhTimeout = System.nanoTime(); this.ecdhKeyPair = CryptoECC.generateKeyPair(eccSpec, secureRandom); @@ -132,6 +132,7 @@ class RegistrationRemoteHandlerServerTCP extends Registrat /** * STEP 3-XXXXX: We pass registration messages around until we the registration handshake is complete! */ + @SuppressWarnings("Duplicates") @Override public void channelRead(ChannelHandlerContext context, Object message) throws Exception { @@ -232,7 +233,7 @@ class RegistrationRemoteHandlerServerTCP extends Registrat // now we have to setup the TEMP AES key! metaChannel.aesKey = new byte[32]; // 256bit keysize (32 bytes) - metaChannel.aesIV = new byte[16]; // 128bit blocksize (16 bytes) + metaChannel.aesIV = new byte[12]; // 96bit blocksize (12 bytes) required by AES-GCM secureRandom.nextBytes(metaChannel.aesKey); secureRandom.nextBytes(metaChannel.aesIV); @@ -245,11 +246,11 @@ class RegistrationRemoteHandlerServerTCP extends Registrat register.eccParameters = CryptoECC.generateSharedParameters(secureRandom); register.aesIV = metaChannel.aesIV; register.aesKey = CryptoECC.encrypt(encrypt, - registrationWrapper2.getPrivateKey(), - metaChannel.publicKey, - register.eccParameters, - metaChannel.aesKey, - logger); + registrationWrapper2.getPrivateKey(), + metaChannel.publicKey, + register.eccParameters, + metaChannel.aesKey, + logger); // now encrypt payload via AES @@ -282,6 +283,7 @@ class RegistrationRemoteHandlerServerTCP extends Registrat registration.payload, logger); + // abort if we cannot properly get the key info from the payload if (payload.length == 0) { logger2.error("Invalid decryption of payload. Aborting."); shutdown(registrationWrapper2, channel); @@ -290,8 +292,11 @@ class RegistrationRemoteHandlerServerTCP extends Registrat return; } + /* + * Diffie-Hellman-Merkle key exchange for the AES key + * see http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange + */ ECPublicKeyParameters ecdhPubKey = EccPublicKeySerializer.read(new Input(payload)); - if (ecdhPubKey == null) { logger2.error("Invalid decode of ecdh public key. Aborting."); shutdown(registrationWrapper2, channel); @@ -319,7 +324,12 @@ class RegistrationRemoteHandlerServerTCP extends Registrat sha384.doFinal(digest, 0); metaChannel.aesKey = Arrays.copyOfRange(digest, 0, 32); // 256bit keysize (32 bytes) - metaChannel.aesIV = Arrays.copyOfRange(digest, 32, 48); // 128bit blocksize (16 bytes) + metaChannel.aesIV = Arrays.copyOfRange(digest, 32, 44); // 96bit blocksize (12 bytes) required by AES-GCM + + // abort if something messed up! + if (verifyAesInfo(message, channel, registrationWrapper2, metaChannel, logger2)) { + return; + } // tell the client to continue it's registration process. channel.writeAndFlush(new Registration()); @@ -330,7 +340,7 @@ class RegistrationRemoteHandlerServerTCP extends Registrat channel.writeAndFlush(registration); // causes client to setup network connection & AES setupConnectionCrypto(metaChannel); - // AES ENCRPYTION NOW USED + // AES ENCRYPTION NOW USED // this sets up the pipeline for the server, so all the necessary handlers are ready to go establishConnection(metaChannel);