diff --git a/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/handler/ProxyMessageAuthHandler.java b/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/handler/ProxyMessageAuthHandler.java index ff41bc10..672576aa 100644 --- a/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/handler/ProxyMessageAuthHandler.java +++ b/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/handler/ProxyMessageAuthHandler.java @@ -1,6 +1,7 @@ package org.dromara.neutrinoproxy.client.handler; import io.netty.channel.ChannelHandlerContext; +import io.netty.util.Attribute; import lombok.extern.slf4j.Slf4j; import org.dromara.neutrinoproxy.client.config.ProxyConfig; import org.dromara.neutrinoproxy.core.Constants; @@ -8,6 +9,7 @@ import org.dromara.neutrinoproxy.core.ExceptionEnum; import org.dromara.neutrinoproxy.core.ProxyMessage; import org.dromara.neutrinoproxy.core.ProxyMessageHandler; import org.dromara.neutrinoproxy.core.dispatcher.Match; +import org.dromara.neutrinoproxy.core.util.SmEncryptUtil; import org.noear.snack.ONode; import org.noear.solon.Solon; import org.noear.solon.annotation.Component; @@ -42,5 +44,17 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler { ){ context.channel().close(); } + + // 获取认证成功的后的公钥信息,并生成随机密码,加密发到服务端确认 + String publicKey = load.get("publicKey").getString(); + byte[] secureKey = SmEncryptUtil.generateSm4Key(); + // 存储密码 + Attribute secureKeyAttr = context.attr(Constants.SECURE_KEY); + secureKeyAttr.set(secureKey); + + // 使用SM2算法对密钥进行加密并发送到服务端 + byte[] encryptSecureKey = SmEncryptUtil.encryptBySm2(publicKey, secureKey); + context.writeAndFlush(ProxyMessage.buildSecureKeyMessage(encryptSecureKey)); + context.flush(); } } diff --git a/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/handler/ProxyMessageSecureKeyHandler.java b/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/handler/ProxyMessageSecureKeyHandler.java new file mode 100644 index 00000000..05a5361e --- /dev/null +++ b/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/handler/ProxyMessageSecureKeyHandler.java @@ -0,0 +1,30 @@ +package org.dromara.neutrinoproxy.client.handler; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.Attribute; +import lombok.extern.slf4j.Slf4j; +import org.dromara.neutrinoproxy.core.Constants; +import org.dromara.neutrinoproxy.core.ProxyMessage; +import org.dromara.neutrinoproxy.core.ProxyMessageHandler; +import org.dromara.neutrinoproxy.core.dispatcher.Match; +import org.dromara.neutrinoproxy.core.util.SmEncryptUtil; +import org.noear.solon.annotation.Component; + +@Slf4j +@Match(type = Constants.ProxyDataTypeName.SECURE_KEY) +@Component +public class ProxyMessageSecureKeyHandler implements ProxyMessageHandler { + @Override + public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) { + Attribute secureKeyAttr = ctx.attr(Constants.SECURE_KEY); + byte[] secureKey = secureKeyAttr.get(); + byte[] data = proxyMessage.getData(); + byte[] decryptedData = SmEncryptUtil.decryptBySm4(secureKey, data); + String m = new String(decryptedData); + if ("ok".equals(m)) { + log.info("Successfully established encrypted link"); + } else { + ctx.channel().close(); + } + } +} diff --git a/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/Constants.java b/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/Constants.java index 472f8ce9..ab831211 100644 --- a/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/Constants.java +++ b/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/Constants.java @@ -38,6 +38,10 @@ public interface Constants { AttributeKey VISITOR_ID = AttributeKey.newInstance("visitor_id"); + AttributeKey SECURE_PRIVATE_KEY = AttributeKey.newInstance("secure_private_key"); + + AttributeKey SECURE_KEY = AttributeKey.newInstance("secure_key"); + AttributeKey LICENSE_ID = AttributeKey.newInstance("license_id"); AttributeKey TARGET_IP = AttributeKey.newInstance("targetIp"); @@ -57,6 +61,7 @@ public interface Constants { interface ProxyDataTypeName { String HEARTBEAT = "HEARTBEAT"; + String SECURE_KEY = "SECURE_KEY"; String AUTH = "AUTH"; String CONNECT = "CONNECT"; String DISCONNECT = "DISCONNECT"; diff --git a/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/KeyPairRecord.java b/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/KeyPairRecord.java new file mode 100644 index 00000000..4826047d --- /dev/null +++ b/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/KeyPairRecord.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.dromara.neutrinoproxy.core; + +/** + * 存储公钥和私钥 + * @param privateKey + * @param publicKey + */ +public record KeyPairRecord(String privateKey, String publicKey) { +} diff --git a/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/ProxyDataTypeEnum.java b/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/ProxyDataTypeEnum.java index e533bf8e..4c30d888 100644 --- a/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/ProxyDataTypeEnum.java +++ b/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/ProxyDataTypeEnum.java @@ -47,7 +47,9 @@ public enum ProxyDataTypeEnum { PORT_MAPPING_SYNC(0x07, Constants.ProxyDataTypeName.PORT_MAPPING_SYNC, "PORT_MAPPING_SYNC"), UDP_CONNECT(0x08, Constants.ProxyDataTypeName.UDP_CONNECT,"UDP_CONNECT"), UDP_DISCONNECT(0x09, Constants.ProxyDataTypeName.UDP_DISCONNECT,"UDP_DISCONNECT"), - UDP_TRANSFER(0x10, Constants.ProxyDataTypeName.UDP_TRANSFER,"UDP_TRANSFER"); + UDP_TRANSFER(0x10, Constants.ProxyDataTypeName.UDP_TRANSFER,"UDP_TRANSFER"), + SECURE_KEY(0x11, Constants.ProxyDataTypeName.SECURE_KEY, "SECURE_KEY"), + ; private static Map cache = Stream.of(values()).collect(Collectors.toMap(ProxyDataTypeEnum::getType, Function.identity())); private int type; diff --git a/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/ProxyMessage.java b/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/ProxyMessage.java index 78de8f56..0da050fe 100644 --- a/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/ProxyMessage.java +++ b/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/ProxyMessage.java @@ -24,6 +24,7 @@ package org.dromara.neutrinoproxy.core; import lombok.Data; import lombok.experimental.Accessors; +import org.dromara.neutrinoproxy.core.util.SmEncryptUtil; import org.noear.snack.ONode; import java.util.Arrays; @@ -79,6 +80,11 @@ public class ProxyMessage { */ public static final byte TYPE_UDP_TRANSFER = 0x10; + /** + * 安全密钥协商 + */ + public static final byte TYPE_SECURE_KEY = 0x11; + /** * 消息类型 */ @@ -117,11 +123,12 @@ public class ProxyMessage { .setInfo(info + "," + clientId); } - public static ProxyMessage buildAuthResultMessage(Integer code, String msg, String licenseKey) { + public static ProxyMessage buildAuthResultMessage(Integer code, String msg, String licenseKey, String publicKey) { ONode data = ONode.newObject(); data.set("code", code); data.set("msg", msg); data.set("licenseKey", licenseKey); + data.set("publicKey", publicKey); return create().setType(TYPE_AUTH) .setInfo(data.toJson()); } @@ -136,6 +143,17 @@ public class ProxyMessage { .setInfo(info); } + public static ProxyMessage buildSecureKeyMessage(byte[] secureKey) { + return create().setType(TYPE_SECURE_KEY) + .setInfo(SmEncryptUtil.digestBySm3(secureKey)) + .setData(secureKey); + } + + public static ProxyMessage buildSecureKeyReturnMessage(byte[] content) { + return create().setType(TYPE_SECURE_KEY) + .setData(content); + } + public static ProxyMessage buildTransferMessage(String visitorId, byte[] data) { return create().setType(TYPE_TRANSFER) .setInfo(visitorId) diff --git a/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/util/SmEncryptUtil.java b/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/util/SmEncryptUtil.java index 8bf5ab86..10c67de4 100644 --- a/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/util/SmEncryptUtil.java +++ b/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/util/SmEncryptUtil.java @@ -25,7 +25,7 @@ package org.dromara.neutrinoproxy.core.util; import cn.hutool.core.util.HexUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.SmUtil; -import lombok.Data; +import org.dromara.neutrinoproxy.core.KeyPairRecord; import javax.crypto.SecretKey; import java.security.KeyPair; @@ -37,14 +37,11 @@ import java.security.KeyPair; */ public class SmEncryptUtil { - record Sm2KeyPairRecord(String privateKey, String publicKey) { - } - /** * 生成SM2密钥对 * @return */ - public static Sm2KeyPairRecord generateSm2KeyPair() { + public static KeyPairRecord generateSm2KeyPair() { KeyPair keyPair = SecureUtil.generateKeyPair("SM2"); byte[] privateKeyBytes = keyPair.getPrivate().getEncoded(); byte[] publicKeyBytes = keyPair.getPublic().getEncoded(); @@ -52,7 +49,7 @@ public class SmEncryptUtil { String privateKey = HexUtil.encodeHexStr(privateKeyBytes); String publicKey = HexUtil.encodeHexStr(publicKeyBytes); - return new Sm2KeyPairRecord(privateKey, publicKey); + return new KeyPairRecord(privateKey, publicKey); } /** @@ -100,4 +97,13 @@ public class SmEncryptUtil { return SmUtil.sm4(key).decrypt(encryptedData); } + /** + * 使用SM3算法对内容生成摘要 + * @param data + * @return + */ + public static String digestBySm3(byte[] data) { + return SmUtil.sm3().digestHex(data); + } + } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageAuthHandler.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageAuthHandler.java index 2fc7a678..bc33dedd 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageAuthHandler.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageAuthHandler.java @@ -23,9 +23,10 @@ package org.dromara.neutrinoproxy.server.proxy.handler; import cn.hutool.core.util.StrUtil; -import org.dromara.neutrinoproxy.core.*; +import io.netty.util.Attribute; import org.dromara.neutrinoproxy.core.*; import org.dromara.neutrinoproxy.core.dispatcher.Match; +import org.dromara.neutrinoproxy.core.util.SmEncryptUtil; import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig; import org.dromara.neutrinoproxy.server.constant.ClientConnectTypeEnum; import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum; @@ -90,7 +91,7 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler { if (StrUtil.isEmpty(licenseKey)) { log.warn("[client connection] license cannot empty info:{} ", info); - ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "license不能为空!", licenseKey)); + ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "license不能为空!", licenseKey, null)); ctx.channel().close(); clientConnectRecordService.add(new ClientConnectRecordDO() .setIp(ip) @@ -105,7 +106,7 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler { LicenseDO licenseDO = licenseService.findByKey(licenseKey); if (null == licenseDO) { log.warn("[client connection] license notfound info:{} ", info); - ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "license不存在!", licenseKey)); + ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "license不存在!", licenseKey, null)); ctx.channel().close(); clientConnectRecordService.add(new ClientConnectRecordDO() .setIp(ip) @@ -119,7 +120,7 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler { } if (EnableStatusEnum.DISABLE.getStatus().equals(licenseDO.getEnable())) { log.warn("[client connection] the license disabled info:{} ", info); - ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "the license disabled!", licenseKey)); + ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "the license disabled!", licenseKey, null)); ctx.channel().close(); clientConnectRecordService.add(new ClientConnectRecordDO() .setIp(ip) @@ -134,7 +135,7 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler { UserDO userDO = userService.findById(licenseDO.getUserId()); if (null == userDO || EnableStatusEnum.DISABLE.getStatus().equals(userDO.getEnable())) { log.warn("[client connection] the license invalid info:{} ", info); - ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "the license invalid!", licenseKey)); + ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "the license invalid!", licenseKey, null)); ctx.channel().close(); clientConnectRecordService.add(new ClientConnectRecordDO() .setIp(ip) @@ -151,7 +152,7 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler { String _clientId = ProxyUtil.getClientIdByLicenseId(licenseDO.getId()); if (!clientId.equals(_clientId)) { log.warn("[client connection] the license on another no used info:{} _clientId:{}", info, _clientId); - ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "the license on another no used!", licenseKey)); + ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "the license on another no used!", licenseKey, null)); ctx.channel().close(); clientConnectRecordService.add(new ClientConnectRecordDO() .setIp(ip) @@ -164,8 +165,16 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler { return; } } + + // 生成获取SM2密钥对,私钥存入ctx,公钥拼装参数随Auth数据包返回 + KeyPairRecord record = SmEncryptUtil.generateSm2KeyPair(); + + // 私钥存入ctx + Attribute attr = ctx.attr(Constants.SECURE_PRIVATE_KEY); + attr.setIfAbsent(record.privateKey()); + // 发送认证成功消息 - ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.SUCCESS.getCode(), "auth success!", licenseKey)); + ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.SUCCESS.getCode(), "auth success!", licenseKey, record.publicKey())); clientConnectRecordService.add(new ClientConnectRecordDO() .setIp(ip) diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageSecureKeyHandler.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageSecureKeyHandler.java new file mode 100644 index 00000000..587d548a --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageSecureKeyHandler.java @@ -0,0 +1,65 @@ +package org.dromara.neutrinoproxy.server.proxy.handler; + +import cn.hutool.core.util.StrUtil; +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.Attribute; +import io.netty.util.AttributeKey; +import lombok.extern.slf4j.Slf4j; +import org.dromara.neutrinoproxy.core.Constants; +import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum; +import org.dromara.neutrinoproxy.core.ProxyMessage; +import org.dromara.neutrinoproxy.core.ProxyMessageHandler; +import org.dromara.neutrinoproxy.core.dispatcher.Match; +import org.dromara.neutrinoproxy.core.util.SmEncryptUtil; +import org.noear.solon.annotation.Component; + +@Slf4j +@Match(type= Constants.ProxyDataTypeName.SECURE_KEY) +@Component +public class ProxyMessageSecureKeyHandler implements ProxyMessageHandler { + + @Override + public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) { + + // data为加密后的密码,info为加密密码的摘要 + byte[] data = proxyMessage.getData(); + String receivedDigest = proxyMessage.getInfo(); + + String digest = SmEncryptUtil.digestBySm3(data); + if (!digest.equals(receivedDigest)) { + // 获取加密信息失败 + log.warn("密码协商失败"); + // TODO 应该断开连接 + return; + } + + // 获取私钥 + Attribute privateKeyAttr = ctx.attr(Constants.SECURE_PRIVATE_KEY); + String privateKey = privateKeyAttr.get(); + if (StrUtil.isEmpty(privateKey)) { + // 获取私钥失败 + log.warn("获取私钥失败"); + // TODO 应该断开连接 + return; + } + + // 解密传输密码 + byte[] secureKey = SmEncryptUtil.decryptBySm2(privateKey, data); + + // 传输密码存储ctx中 + Attribute secureKeyAttr = ctx.attr(Constants.SECURE_KEY); + secureKeyAttr.setIfAbsent(secureKey); + + // 使用密码加密success给客户端表示密码已确认 + byte[] encryptedSuccessInfoData = SmEncryptUtil.encryptBySm4(secureKey, "ok".getBytes()); + + // 发送回去,以示确认 + ctx.writeAndFlush(ProxyMessage.buildSecureKeyReturnMessage(encryptedSuccessInfoData)); + ctx.flush(); + } + + @Override + public String name() { + return ProxyDataTypeEnum.SECURE_KEY.getDesc(); + } +}