From ae116d3dfe8301f3e08a279b733581725708206f Mon Sep 17 00:00:00 2001 From: Dennis Sheirer Date: Fri, 18 Aug 2023 07:06:26 -0400 Subject: [PATCH] #1630 Updates ARC4 FLC encryption parameters message to detect and parse both Hytera & Motorola message formats. --- .../dmr/message/data/lc/LCMessageFactory.java | 33 ++++- .../decode/dmr/message/data/lc/LCOpcode.java | 2 +- .../HyteraArc4EncryptionParameters.java | 117 ++++++++++++++++++ ... => MotorolaArc4EncryptionParameters.java} | 34 ++--- 4 files changed, 159 insertions(+), 27 deletions(-) create mode 100644 src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraArc4EncryptionParameters.java rename src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/{MotorolaEncryptionParameters.java => MotorolaArc4EncryptionParameters.java} (77%) diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java index 4f5f1dc33..92669470a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java @@ -33,12 +33,13 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TerminatorData; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnknownFullLCMessage; +import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraArc4EncryptionParameters; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraTerminator; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser; -import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaEncryptionParameters; +import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaArc4EncryptionParameters; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser; import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ActivityUpdateMessage; import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel; @@ -122,8 +123,34 @@ else if(message.size() == 96) case FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER: flc = new CapacityPlusWideAreaVoiceChannelUser(message, timestamp, timeslot); break; - case FULL_CAPACITY_PLUS_ENCRYPTION_PARAMETERS: - flc = new MotorolaEncryptionParameters(message, timestamp, timeslot); + case FULL_ARC4_ENCRYPTION_PARAMETERS: + boolean isHytera = false; + + //This is apparently now a DMR standard FLC opcode, even though it's using Motorola's vendor ID (0x10). + //As observed on a known Hytera system, it's not using the standard FLC RS-12/9/4 check, which fails. + //It's apparently using the CRC-CCITT with 0x9696 initial fill. Not sure if both Hytera and Motorola + // are using this FEC. But, by using only 16 bits for checksum versus the standad 24, they're able to + // include a full 24-bit group/radio ID in the message. For now we'll identify as either Motorola + //(using reed solomon) or Hytera (using crc-ccitt). + if(!valid) + { + int bitErrors = CRCDMR.correctCCITT80(message, 0, 80, 0x9696); + + if(bitErrors < 2) + { + valid = true; + isHytera = true; + } + } + + if(isHytera) + { + flc = new HyteraArc4EncryptionParameters(message, timestamp, timeslot); + } + else + { + flc = new MotorolaArc4EncryptionParameters(message, timestamp, timeslot); + } break; case FULL_HYTERA_GROUP_VOICE_CHANNEL_USER: flc = new HyteraGroupVoiceChannelUser(message, timestamp, timeslot); diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java index 7c886f170..e5377e4be 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java @@ -49,7 +49,7 @@ public enum LCOpcode //Observed on Cap+ Multi-Site System during an encrypted voice call FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 32, "ENCRYPTED VOICE CHANNEL USER"), //Observed on Cap+ Multi-Site System during an encrypted voice call - FULL_CAPACITY_PLUS_ENCRYPTION_PARAMETERS(Vendor.MOTOROLA_CAPACITY_PLUS, true, 33, "ENCRYPTION PARAMETERS"), + FULL_ARC4_ENCRYPTION_PARAMETERS(Vendor.MOTOROLA_CAPACITY_PLUS, true, 33, "ARC4/EP ENCRYPTION PARAMETERS"), //Cap+ opcodes from https://forums.radioreference.com/threads/understanding-capacity-plus-trunking-some-more.452566/ //FLCO 0: Group Call Maintenance //FLCO 3: Private Call Maintenance (TermLC) diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraArc4EncryptionParameters.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraArc4EncryptionParameters.java new file mode 100644 index 000000000..3efc4ca7f --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraArc4EncryptionParameters.java @@ -0,0 +1,117 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; +import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage; +import java.util.ArrayList; +import java.util.List; + +/** + * Hytera ARC4/EP Encryption Parameters + *

+ * Note: observed as FLC payload for a PI_HEADER slot type. + * Note: observed on a Hytera system that was configured as IP Site Connect compatible. + */ +public class HyteraArc4EncryptionParameters extends FullLCMessage +{ + private static final int[] KEY_ID = new int[]{16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] INITIALIZATION_VECTOR = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] DESTINATION_GROUP = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + 71, 72, 73, 74, 75, 76, 77, 78, 79}; + //Hytera version uses CRC-CCITT with 0x9696 initial fill, bits: 80-95 + + private DMRTalkgroup mTalkgroup; + private List mIdentifiers; + + /** + * Constructor + * @param message bits + * @param timestamp for the message + * @param timeslot of the message + */ + public HyteraArc4EncryptionParameters(CorrectedBinaryMessage message, long timestamp, int timeslot) + { + super(message, timestamp, timeslot); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(!isValid()) + { + sb.append("[CRC-ERROR] "); + } + + if(isEncrypted()) + { + sb.append(" *ENCRYPTED*"); + } + + if(isReservedBitSet()) + { + sb.append(" *RESERVED-BIT*"); + } + + sb.append("FLC HYTERA ARC4/EP ENCRYPTION PARAMETERS"); + sb.append(" KEY:").append(getKeyId()); + sb.append(" IV:").append(getInitializationVector()); + sb.append(" TALKGROUP:").append(getTalkgroup()); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + public DMRTalkgroup getTalkgroup() + { + if(mTalkgroup == null) + { + mTalkgroup = new DMRTalkgroup(getMessage().getInt(DESTINATION_GROUP)); + } + + return mTalkgroup; + } + + public int getKeyId() + { + return getMessage().getInt(KEY_ID); + } + + public String getInitializationVector() + { + return getMessage().getHex(INITIALIZATION_VECTOR, 8); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTalkgroup()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/MotorolaEncryptionParameters.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/MotorolaArc4EncryptionParameters.java similarity index 77% rename from src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/MotorolaEncryptionParameters.java rename to src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/MotorolaArc4EncryptionParameters.java index ae5d53188..a1c0d9f11 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/MotorolaEncryptionParameters.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/MotorolaArc4EncryptionParameters.java @@ -27,29 +27,29 @@ import java.util.List; /** - * Motorola Encryption Parameters + * Motorola ARC4/EP Encryption Parameters *

* Note: observed as FLC payload for a PI_HEADER slot type. * Note: observed on a possible Hytera (clone) system that was configured as IP Site Connect compatible. */ -public class MotorolaEncryptionParameters extends FullLCMessage +public class MotorolaArc4EncryptionParameters extends FullLCMessage { private static final int[] KEY_ID = new int[]{16, 17, 18, 19, 20, 21, 22, 23}; private static final int[] INITIALIZATION_VECTOR = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; - private static final int[] ALGORITHM = new int[]{56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] DESTINATION_GROUP = new int[]{64, 65, 66, 67, 68, 69, 70, 71}; - //Reed Solomon FEC: 72-95 + private static final int[] DESTINATION_GROUP = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; + //Motorola Reed Solomon FEC bits: 72-95 private DMRTalkgroup mTalkgroup; private List mIdentifiers; /** - * Constructs an instance. - * - * @param message for the link control payload + * Constructor + * @param message bits + * @param timestamp for the message + * @param timeslot of the message */ - public MotorolaEncryptionParameters(CorrectedBinaryMessage message, long timestamp, int timeslot) + public MotorolaArc4EncryptionParameters(CorrectedBinaryMessage message, long timestamp, int timeslot) { super(message, timestamp, timeslot); } @@ -74,9 +74,9 @@ public String toString() sb.append(" *RESERVED-BIT*"); } - sb.append("FLC MOTOROLA ENCRYPTION PARAMETERS - ALGORITHM:").append(getAlgorithm()); + sb.append("FLC MOTOROLA ARC4/EP ENCRYPTION PARAMETERS"); sb.append(" KEY:").append(getKeyId()); - sb.append(" IV?:").append(getInitializationVector()); + sb.append(" IV:").append(getInitializationVector()); sb.append(" TALKGROUP:").append(getTalkgroup()); sb.append(" MSG:").append(getMessage().toHexString()); return sb.toString(); @@ -97,18 +97,6 @@ public int getKeyId() return getMessage().getInt(KEY_ID); } - public String getAlgorithm() - { - int algorithm = getMessage().getInt(ALGORITHM); - - if(algorithm == 0) - { - return "EP/ARC4"; - } - - return "UNK(" + algorithm + ")"; - } - public String getInitializationVector() { return getMessage().getHex(INITIALIZATION_VECTOR, 8);