密码协商过程
This commit is contained in:
+14
@@ -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<byte[]> secureKeyAttr = context.attr(Constants.SECURE_KEY);
|
||||
secureKeyAttr.set(secureKey);
|
||||
|
||||
// 使用SM2算法对密钥进行加密并发送到服务端
|
||||
byte[] encryptSecureKey = SmEncryptUtil.encryptBySm2(publicKey, secureKey);
|
||||
context.writeAndFlush(ProxyMessage.buildSecureKeyMessage(encryptSecureKey));
|
||||
context.flush();
|
||||
}
|
||||
}
|
||||
|
||||
+30
@@ -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<byte[]> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,10 @@ public interface Constants {
|
||||
|
||||
AttributeKey<String> VISITOR_ID = AttributeKey.newInstance("visitor_id");
|
||||
|
||||
AttributeKey<String> SECURE_PRIVATE_KEY = AttributeKey.newInstance("secure_private_key");
|
||||
|
||||
AttributeKey<byte[]> SECURE_KEY = AttributeKey.newInstance("secure_key");
|
||||
|
||||
AttributeKey<Integer> LICENSE_ID = AttributeKey.newInstance("license_id");
|
||||
|
||||
AttributeKey<String> 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";
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
+3
-1
@@ -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<Integer,ProxyDataTypeEnum> cache = Stream.of(values()).collect(Collectors.toMap(ProxyDataTypeEnum::getType, Function.identity()));
|
||||
|
||||
private int type;
|
||||
|
||||
@@ -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)
|
||||
|
||||
+12
-6
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+16
-7
@@ -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<String> 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)
|
||||
|
||||
+65
@@ -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<String> 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<byte[]> 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user