From b4d163e144546c5e517976d6de024893b8bbb2f7 Mon Sep 17 00:00:00 2001 From: heavyrian2012 Date: Fri, 18 Nov 2022 16:01:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0android=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF=E7=AD=BE=E5=90=8D=E9=AA=8C=E8=AF=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- broker/config/wildfirechat.conf | 6 +++ .../java/io/moquette/BrokerConstants.java | 3 ++ .../persistence/MemoryMessagesStore.java | 43 +++++++++++++++++++ .../java/io/moquette/spi/IMessagesStore.java | 2 + .../moquette/spi/impl/ProtocolProcessor.java | 24 +++++++++++ .../codec/mqtt/MqttConnectPayload.java | 13 ++++-- .../codec/mqtt/MqttConnectReturnCode.java | 1 + .../netty/handler/codec/mqtt/MqttDecoder.java | 13 ++++-- .../codec/mqtt/MqttMessageBuilders.java | 2 +- .../src/main/resources/wildfirechat.conf | 6 +++ 10 files changed, 106 insertions(+), 7 deletions(-) diff --git a/broker/config/wildfirechat.conf b/broker/config/wildfirechat.conf index b210ddc8..04407681 100755 --- a/broker/config/wildfirechat.conf +++ b/broker/config/wildfirechat.conf @@ -59,6 +59,12 @@ token.key testim ##token的过期时间,单位为毫秒,默认为无限期。如果需要设置无限期,客户端上一定需要加上token过期的处理。token过期的处理请参考文档的常见问题 ##token.expire_time 2592000000 +##客户端(目前只有android)的应用签名(一个应用最取第一个签名),如果有多个签名(多个客户端),用英文逗号分开。获取签名方法请参考文档:https://docs.wildfirechat.cn/blogs/签名验证.html +#connect.client_signature_list eNkezQ1g9OsnhfLSFUY1vzKDzhs=,xykezQ1g9OsnhfLSFUY1vzKDzhs= + +##是否拒绝空签名的android客户端, 默认为true。当有旧的客户端需要兼容时改成false,当所有都是新客户端时,改成true。 +#connect.reject_empty_signature true + ##首次登录,是否接收之前的历史消息。 ##0 不接收历史消息,只接收${message.compensate_time_limit}毫秒以内的消息,由于服务器没有保存已经收取记录,所以如果有超过${message.compensate_time_limit}毫秒之前未收取的消息也不会收取下拉; ##1接收,会接收 message.max_queue 配置的条数的历史消息 diff --git a/broker/src/main/java/io/moquette/BrokerConstants.java b/broker/src/main/java/io/moquette/BrokerConstants.java index dda4f945..0004146b 100755 --- a/broker/src/main/java/io/moquette/BrokerConstants.java +++ b/broker/src/main/java/io/moquette/BrokerConstants.java @@ -110,6 +110,9 @@ public final class BrokerConstants { public static final String TOKEN_SECRET_KEY = "token.key"; public static final String TOKEN_EXPIRE_TIME = "token.expire_time"; + public static final String CONNECT_CLIENT_SIGNATURE_LIST = "connect.client_signature_list"; + public static final String CONNECT_REJECT_EMPTY_SIGNATURE = "connect.reject_empty_signature"; + public static final String EMBED_DB_PROPERTY_NAME = "embed.db"; public static final String DB_AUTO_CLEAN_HISTORY_MESSAGES = "db.auto_clean_history_messages"; public static final String H2DB_PATH = "h2db.path"; diff --git a/broker/src/main/java/io/moquette/persistence/MemoryMessagesStore.java b/broker/src/main/java/io/moquette/persistence/MemoryMessagesStore.java index b384a70f..589bbf23 100755 --- a/broker/src/main/java/io/moquette/persistence/MemoryMessagesStore.java +++ b/broker/src/main/java/io/moquette/persistence/MemoryMessagesStore.java @@ -147,6 +147,9 @@ public class MemoryMessagesStore implements IMessagesStore { private boolean mDisableStrangerChat = false; private Set mAllowStrangerChatSet = new HashSet<>(); + private Set mClientSignatureSet = new HashSet<>(); + private boolean mRejectEmptySignature = true; + private long mChatroomParticipantIdleTime = 900000; private boolean mChatroomRejoinWhenActive = true; private boolean mChatroomCreateWhenNotExist = true; @@ -191,6 +194,27 @@ public class MemoryMessagesStore implements IMessagesStore { IS_CHATROOM_MESSAGE_REMOTE_HISTORY_MESSAGE = "1".equals(m_Server.getConfig().getProperty(MESSAGE_Remote_Chatroom_History_Message, "1")); Constants.MAX_MESSAGE_QUEUE = Integer.parseInt(m_Server.getConfig().getProperty(MESSAGE_Max_Queue)); + try { + String signatureList = m_Server.getConfig().getProperty(BrokerConstants.CONNECT_CLIENT_SIGNATURE_LIST); + if(!StringUtil.isNullOrEmptyAfterTrim(signatureList)) { + signatureList = signatureList.replace(",", ","); + for (String s : signatureList.split(",")) { + mClientSignatureSet.add(s.trim()); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + try { + if(!mClientSignatureSet.isEmpty()) { + mRejectEmptySignature = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.CONNECT_REJECT_EMPTY_SIGNATURE, "true")); + } else { + mRejectEmptySignature = false; + } + } catch (Exception e) { + } + try { mDisableStrangerChat = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.MESSAGE_Disable_Stranger_Chat)); } catch (Exception e) { @@ -4330,6 +4354,25 @@ public class MemoryMessagesStore implements IMessagesStore { return UUIDGenerator.getUUID(); } + @Override + public boolean checkSignature(String signature) { + if(StringUtil.isNullOrEmptyAfterTrim(signature)) { + if(mRejectEmptySignature) { + LOG.info("Failed check signature, no signature"); + return false; + } else { + return true; + } + } else { + if(mClientSignatureSet.contains(signature)) { + return true; + } else { + LOG.info("Failed check signature, signature no matched"); + return false; + } + } + } + @Override public void storeRetained(Topic topic, StoredMessage storedMessage) { LOG.debug("Store retained message for topic={}, CId={}", topic, storedMessage.getClientID()); diff --git a/broker/src/main/java/io/moquette/spi/IMessagesStore.java b/broker/src/main/java/io/moquette/spi/IMessagesStore.java index a8295926..f94268dd 100755 --- a/broker/src/main/java/io/moquette/spi/IMessagesStore.java +++ b/broker/src/main/java/io/moquette/spi/IMessagesStore.java @@ -249,6 +249,8 @@ public interface IMessagesStore { //使用了数据库,会比较慢,仅能用户用户/群组等id的生成 String getShortUUID(); + + boolean checkSignature(String signature); /** * Used to initialize all persistent store structures */ diff --git a/broker/src/main/java/io/moquette/spi/impl/ProtocolProcessor.java b/broker/src/main/java/io/moquette/spi/impl/ProtocolProcessor.java index 36d1184d..a041adda 100755 --- a/broker/src/main/java/io/moquette/spi/impl/ProtocolProcessor.java +++ b/broker/src/main/java/io/moquette/spi/impl/ProtocolProcessor.java @@ -48,6 +48,7 @@ import win.liyufan.im.HttpUtils; import win.liyufan.im.IMTopic; import win.liyufan.im.Utility; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -339,6 +340,24 @@ public class ProtocolProcessor { failedCredentials(channel); return false; } + + if(session.getPlatform() == ProtoConstants.Platform.Platform_Android || session.getPlatform() == ProtoConstants.Platform.Platform_APad) { + byte[] signature = msg.payload().signatureInBytes(); + String basedSign = null; + if (signature != null && signature.length > 0) { + signature = AES.AESDecrypt(signature, session.getSecret(), true); + } + if (signature != null && signature.length > 0) { + basedSign = Base64.getEncoder().encodeToString(signature); + } + + if (!m_messagesStore.checkSignature(basedSign)) { + LOG.error("Bad signature of session <{}, {}>", session.getUsername(), session.getClientID()); + failedBadSignature(channel); + return false; + } + } + session.setMqttVersion(mqttVersion); } else { LOG.error("Client didn't supply any password and MQTT anonymous mode is disabled CId={}", clientId); @@ -446,6 +465,11 @@ public class ProtocolProcessor { LOG.info("Client {} failed to connect with bad username or password.", session); } + private void failedBadSignature(Channel session) { + session.writeAndFlush(connAck(CONNECTION_REFUSED_SIGNATURE_FAILURE)); + LOG.info("Client {} failed to connect with bad signature.", session); + } + private void setIdleTime(ChannelPipeline pipeline, int idleTime) { if (pipeline.names().contains("idleStateHandler")) { pipeline.remove("idleStateHandler"); diff --git a/broker/src/main/java/io/netty/handler/codec/mqtt/MqttConnectPayload.java b/broker/src/main/java/io/netty/handler/codec/mqtt/MqttConnectPayload.java index 9de27bf1..83b5d2d1 100644 --- a/broker/src/main/java/io/netty/handler/codec/mqtt/MqttConnectPayload.java +++ b/broker/src/main/java/io/netty/handler/codec/mqtt/MqttConnectPayload.java @@ -32,6 +32,7 @@ public final class MqttConnectPayload { private final byte[] willMessage; private final String userName; private final byte[] password; + private final byte[] signature; /** * @deprecated use {@link MqttConnectPayload#MqttConnectPayload(String, @@ -50,7 +51,7 @@ public final class MqttConnectPayload { willTopic, willMessage.getBytes(CharsetUtil.UTF_8), userName, - password.getBytes(CharsetUtil.UTF_8)); + password.getBytes(CharsetUtil.UTF_8), null); } public MqttConnectPayload( @@ -64,7 +65,7 @@ public final class MqttConnectPayload { willTopic, willMessage, userName, - password); + password, null); } public MqttConnectPayload( @@ -73,13 +74,15 @@ public final class MqttConnectPayload { String willTopic, byte[] willMessage, String userName, - byte[] password) { + byte[] password, + byte[] signature) { this.clientIdentifier = clientIdentifier; this.willProperties = MqttProperties.withEmptyDefaults(willProperties); this.willTopic = willTopic; this.willMessage = willMessage; this.userName = userName; this.password = password; + this.signature = signature; } public String clientIdentifier() { @@ -122,6 +125,10 @@ public final class MqttConnectPayload { return password; } + public byte[] signatureInBytes() { + return signature; + } + @Override public String toString() { return new StringBuilder(StringUtil.simpleClassName(this)) diff --git a/broker/src/main/java/io/netty/handler/codec/mqtt/MqttConnectReturnCode.java b/broker/src/main/java/io/netty/handler/codec/mqtt/MqttConnectReturnCode.java index 51134328..c0b307a9 100644 --- a/broker/src/main/java/io/netty/handler/codec/mqtt/MqttConnectReturnCode.java +++ b/broker/src/main/java/io/netty/handler/codec/mqtt/MqttConnectReturnCode.java @@ -29,6 +29,7 @@ public enum MqttConnectReturnCode { CONNECTION_REFUSED_NOT_AUTHORIZED((byte) 0x05), CONNECTION_REFUSED_UNEXPECT_NODE((byte) 0X06), CONNECTION_REFUSED_SESSION_NOT_EXIST((byte) 0x07), + CONNECTION_REFUSED_SIGNATURE_FAILURE((byte) 0x08), //MQTT 5 codes CONNECTION_REFUSED_UNSPECIFIED_ERROR((byte) 0x80), CONNECTION_REFUSED_MALFORMED_PACKET((byte) 0x81), diff --git a/broker/src/main/java/io/netty/handler/codec/mqtt/MqttDecoder.java b/broker/src/main/java/io/netty/handler/codec/mqtt/MqttDecoder.java index bab60a06..5f048060 100644 --- a/broker/src/main/java/io/netty/handler/codec/mqtt/MqttDecoder.java +++ b/broker/src/main/java/io/netty/handler/codec/mqtt/MqttDecoder.java @@ -146,7 +146,7 @@ public final class MqttDecoder extends ReplayingDecoder... * * @param buffer the buffer to decode from * @return the fixed header @@ -534,7 +534,7 @@ public final class MqttDecoder extends ReplayingDecoder propertiesResult = decodeProperties(buffer); willProperties = propertiesResult.value; numberOfBytesConsumed += propertiesResult.numberOfBytesConsumed; @@ -550,6 +550,7 @@ public final class MqttDecoder extends ReplayingDecoder decodedUserName = null; byte[] decodedPassword = null; + byte[] decodedSignature = null; if (mqttConnectVariableHeader.hasUserName()) { decodedUserName = decodeString(buffer); numberOfBytesConsumed += decodedUserName.numberOfBytesConsumed; @@ -558,6 +559,10 @@ public final class MqttDecoder extends ReplayingDecoder(mqttConnectPayload, numberOfBytesConsumed); } diff --git a/broker/src/main/java/io/netty/handler/codec/mqtt/MqttMessageBuilders.java b/broker/src/main/java/io/netty/handler/codec/mqtt/MqttMessageBuilders.java index e6bdb746..f5e5266d 100644 --- a/broker/src/main/java/io/netty/handler/codec/mqtt/MqttMessageBuilders.java +++ b/broker/src/main/java/io/netty/handler/codec/mqtt/MqttMessageBuilders.java @@ -208,7 +208,7 @@ public final class MqttMessageBuilders { keepAliveSecs, properties); MqttConnectPayload mqttConnectPayload = - new MqttConnectPayload(clientId, willProperties, willTopic, willMessage, username, password); + new MqttConnectPayload(clientId, willProperties, willTopic, willMessage, username, password, null); return new MqttConnectMessage(mqttFixedHeader, mqttConnectVariableHeader, mqttConnectPayload); } } diff --git a/distribution/src/main/resources/wildfirechat.conf b/distribution/src/main/resources/wildfirechat.conf index 58b212dc..f7c6f9e3 100755 --- a/distribution/src/main/resources/wildfirechat.conf +++ b/distribution/src/main/resources/wildfirechat.conf @@ -56,6 +56,12 @@ token.key testim ##token的过期时间,单位为毫秒,默认为无限期。如果需要设置无限期,客户端上一定需要加上token过期的处理。token过期的处理请参考文档的常见问题 ##token.expire_time 2592000000 +##客户端(目前只有android)的应用签名(一个应用最取第一个签名),如果有多个签名(多个客户端),用英文逗号分开。获取签名方法请参考文档:https://docs.wildfirechat.cn/blogs/签名验证.html +#connect.client_signature_list eNkezQ1g9OsnhfLSFUY1vzKDzhs=,xykezQ1g9OsnhfLSFUY1vzKDzhs= + +##是否拒绝空签名的android客户端, 默认为true。当有旧的客户端需要兼容时改成false,当所有都是新客户端时,改成true。 +#connect.reject_empty_signature true + ##首次登录,是否接收之前的历史消息。 ##0 不接收历史消息,只接收${message.compensate_time_limit}毫秒以内的消息,由于服务器没有保存已经收取记录,所以如果有超过${message.compensate_time_limit}毫秒之前未收取的消息也不会收取下拉; ##1接收,会接收 message.max_queue 配置的条数的历史消息