Add support to encrypt P25 AES and ADP; add call history window

pull/1/head
firealarmss 11 months ago
parent 6cadbdf897
commit 692ad204e6

@ -0,0 +1,23 @@
<Window x:Class="WhackerLinkConsoleV2.CallHistoryWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WhackerLinkConsoleV2"
mc:Ignorable="d"
Title="Call History" Height="450" Width="351">
<Grid>
<DataGrid ItemsSource="{Binding CallHistory}" AutoGenerateColumns="False" IsReadOnly="True" Margin="10">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background" Value="{Binding BackgroundColor}" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Channel" Binding="{Binding Channel}" Width="*" />
<DataGridTextColumn Header="SrcId" Binding="{Binding SrcId}" Width="*" />
<DataGridTextColumn Header="DstId" Binding="{Binding DstId}" Width="*" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>

@ -0,0 +1,93 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace WhackerLinkConsoleV2
{
public partial class CallHistoryWindow : Window
{
public CallHistoryViewModel ViewModel { get; set; }
public CallHistoryWindow()
{
InitializeComponent();
ViewModel = new CallHistoryViewModel();
DataContext = ViewModel;
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
this.Hide();
}
public void AddCall(string channel, int srcId, int dstId)
{
Dispatcher.Invoke(() =>
{
ViewModel.CallHistory.Insert(0, new CallEntry
{
Channel = channel,
SrcId = srcId,
DstId = dstId,
BackgroundColor = Brushes.Transparent
});
});
}
public void ChannelKeyed(string channel, int srcId, bool encrypted)
{
Dispatcher.Invoke(() =>
{
foreach (var entry in ViewModel.CallHistory.Where(c => c.Channel == channel && c.SrcId == srcId))
{
if (!encrypted)
entry.BackgroundColor = Brushes.LightGreen;
else
entry.BackgroundColor = Brushes.Orange;
}
});
}
public void ChannelUnkeyed(string channel, int srcId)
{
Dispatcher.Invoke(() =>
{
foreach (var entry in ViewModel.CallHistory.Where(c => c.Channel == channel && c.SrcId == srcId))
{
entry.BackgroundColor = Brushes.Transparent;
}
});
}
}
public class CallHistoryViewModel
{
public ObservableCollection<CallEntry> CallHistory { get; set; }
public CallHistoryViewModel()
{
CallHistory = new ObservableCollection<CallEntry>();
}
}
public class CallEntry : DependencyObject
{
public string Channel { get; set; }
public int SrcId { get; set; }
public int DstId { get; set; }
public static readonly DependencyProperty BackgroundColorProperty =
DependencyProperty.Register(nameof(BackgroundColor), typeof(Brush), typeof(CallEntry), new PropertyMetadata(Brushes.Transparent));
public Brush BackgroundColor
{
get { return (Brush)GetValue(BackgroundColorProperty); }
set { SetValue(BackgroundColorProperty, value); }
}
}
}

@ -23,6 +23,8 @@ using fnecore.P25;
using NAudio.Wave;
using System.Windows.Threading;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
namespace WhackerLinkConsoleV2
{
@ -64,9 +66,19 @@ namespace WhackerLinkConsoleV2
return;
}
public void CreateNewP25MessageHdr(byte duid, RemoteCallData callData, ref byte[] data)
public void CreateNewP25MessageHdr(byte duid, RemoteCallData callData, ref byte[] data, byte algId = 0, ushort kId = 0, byte[] mi = null)
{
CreateP25MessageHdr(duid, callData, ref data);
// if an mi is present, this is an encrypted header
if (mi != null)
{
data[14U] |= 0x08; // Control bit
data[181U] = algId; // Algorithm ID
FneUtils.WriteBytes(kId, ref data, 182); // Key ID
Array.Copy(mi, 0, data, 184, P25Defines.P25_MI_LENGTH); // Message Indicator
}
}
/// <summary>
@ -272,7 +284,7 @@ namespace WhackerLinkConsoleV2
/// <param name="offset"></param>
/// <param name="imbe"></param>
/// <param name="frameType"></param>
private void EncodeLDU2(ref byte[] data, int offset, byte[] imbe, byte frameType)
private void EncodeLDU2(ref byte[] data, int offset, byte[] imbe, byte frameType, CryptoParams cryptoParams)
{
if (data == null)
throw new ArgumentNullException("data");
@ -328,33 +340,33 @@ namespace WhackerLinkConsoleV2
break;
case P25DFSI.P25_DFSI_LDU2_VOICE12:
{
dfsiFrame[1U] = 0; // Message Indicator
dfsiFrame[2U] = 0;
dfsiFrame[3U] = 0;
dfsiFrame[1U] = cryptoParams.Mi[0]; // Message Indicator
dfsiFrame[2U] = cryptoParams.Mi[1];
dfsiFrame[3U] = cryptoParams.Mi[2];
Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE
}
break;
case P25DFSI.P25_DFSI_LDU2_VOICE13:
{
dfsiFrame[1U] = 0; // Message Indicator
dfsiFrame[2U] = 0;
dfsiFrame[3U] = 0;
dfsiFrame[1U] = cryptoParams.Mi[3]; // Message Indicator
dfsiFrame[2U] = cryptoParams.Mi[4];
dfsiFrame[3U] = cryptoParams.Mi[5];
Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE
}
break;
case P25DFSI.P25_DFSI_LDU2_VOICE14:
{
dfsiFrame[1U] = 0; // Message Indicator
dfsiFrame[2U] = 0;
dfsiFrame[3U] = 0;
dfsiFrame[1U] = cryptoParams.Mi[6]; // Message Indicator
dfsiFrame[2U] = cryptoParams.Mi[7];
dfsiFrame[3U] = cryptoParams.Mi[8];
Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE
}
break;
case P25DFSI.P25_DFSI_LDU2_VOICE15:
{
dfsiFrame[1U] = P25Defines.P25_ALGO_UNENCRYPT; // Algorithm ID
dfsiFrame[2U] = 0; // Key ID
dfsiFrame[3U] = 0;
dfsiFrame[1U] = cryptoParams.AlgId; // Algorithm ID
FneUtils.WriteBytes(cryptoParams.KeyId, ref dfsiFrame, 2); // Key ID
//dfsiFrame[3U] = 0;
Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE
}
break;
@ -399,46 +411,49 @@ namespace WhackerLinkConsoleV2
/// </summary>
/// <param name="netLDU2">Input LDU data array</param>
/// <param name="data">Output data array</param>
public void CreateP25LDU2Message(in byte[] netLDU2, ref byte[] data)
public void CreateP25LDU2Message(in byte[] netLDU2, ref byte[] data, CryptoParams cryptoParams = null)
{
if (cryptoParams == null)
cryptoParams = new CryptoParams();
// pack DFSI data
int count = P25_MSG_HDR_SIZE;
byte[] imbe = new byte[IMBE_BUF_LEN];
Buffer.BlockCopy(netLDU2, 10, imbe, 0, IMBE_BUF_LEN);
EncodeLDU2(ref data, 24, imbe, P25DFSI.P25_DFSI_LDU2_VOICE10);
EncodeLDU2(ref data, 24, imbe, P25DFSI.P25_DFSI_LDU2_VOICE10, cryptoParams);
count += (int)P25DFSI.P25_DFSI_LDU2_VOICE10_FRAME_LENGTH_BYTES;
Buffer.BlockCopy(netLDU2, 26, imbe, 0, IMBE_BUF_LEN);
EncodeLDU2(ref data, 46, imbe, P25DFSI.P25_DFSI_LDU2_VOICE11);
EncodeLDU2(ref data, 46, imbe, P25DFSI.P25_DFSI_LDU2_VOICE11, cryptoParams);
count += (int)P25DFSI.P25_DFSI_LDU2_VOICE11_FRAME_LENGTH_BYTES;
Buffer.BlockCopy(netLDU2, 55, imbe, 0, IMBE_BUF_LEN);
EncodeLDU2(ref data, 60, imbe, P25DFSI.P25_DFSI_LDU2_VOICE12);
EncodeLDU2(ref data, 60, imbe, P25DFSI.P25_DFSI_LDU2_VOICE12, cryptoParams);
count += (int)P25DFSI.P25_DFSI_LDU2_VOICE12_FRAME_LENGTH_BYTES;
Buffer.BlockCopy(netLDU2, 80, imbe, 0, IMBE_BUF_LEN);
EncodeLDU2(ref data, 77, imbe, P25DFSI.P25_DFSI_LDU2_VOICE13);
EncodeLDU2(ref data, 77, imbe, P25DFSI.P25_DFSI_LDU2_VOICE13, cryptoParams);
count += (int)P25DFSI.P25_DFSI_LDU2_VOICE13_FRAME_LENGTH_BYTES;
Buffer.BlockCopy(netLDU2, 105, imbe, 0, IMBE_BUF_LEN);
EncodeLDU2(ref data, 94, imbe, P25DFSI.P25_DFSI_LDU2_VOICE14);
EncodeLDU2(ref data, 94, imbe, P25DFSI.P25_DFSI_LDU2_VOICE14, cryptoParams);
count += (int)P25DFSI.P25_DFSI_LDU2_VOICE14_FRAME_LENGTH_BYTES;
Buffer.BlockCopy(netLDU2, 130, imbe, 0, IMBE_BUF_LEN);
EncodeLDU2(ref data, 111, imbe, P25DFSI.P25_DFSI_LDU2_VOICE15);
EncodeLDU2(ref data, 111, imbe, P25DFSI.P25_DFSI_LDU2_VOICE15, cryptoParams);
count += (int)P25DFSI.P25_DFSI_LDU2_VOICE15_FRAME_LENGTH_BYTES;
Buffer.BlockCopy(netLDU2, 155, imbe, 0, IMBE_BUF_LEN);
EncodeLDU2(ref data, 128, imbe, P25DFSI.P25_DFSI_LDU2_VOICE16);
EncodeLDU2(ref data, 128, imbe, P25DFSI.P25_DFSI_LDU2_VOICE16, cryptoParams);
count += (int)P25DFSI.P25_DFSI_LDU2_VOICE16_FRAME_LENGTH_BYTES;
Buffer.BlockCopy(netLDU2, 180, imbe, 0, IMBE_BUF_LEN);
EncodeLDU2(ref data, 145, imbe, P25DFSI.P25_DFSI_LDU2_VOICE17);
EncodeLDU2(ref data, 145, imbe, P25DFSI.P25_DFSI_LDU2_VOICE17, cryptoParams);
count += (int)P25DFSI.P25_DFSI_LDU2_VOICE17_FRAME_LENGTH_BYTES;
Buffer.BlockCopy(netLDU2, 204, imbe, 0, IMBE_BUF_LEN);
EncodeLDU2(ref data, 162, imbe, P25DFSI.P25_DFSI_LDU2_VOICE18);
EncodeLDU2(ref data, 162, imbe, P25DFSI.P25_DFSI_LDU2_VOICE18, cryptoParams);
count += (int)P25DFSI.P25_DFSI_LDU2_VOICE18_FRAME_LENGTH_BYTES;
data[23U] = (byte)count;
@ -465,4 +480,14 @@ namespace WhackerLinkConsoleV2
}
}
} // public abstract partial class FneSystemBase : fnecore.FneSystemBase
/// <summary>
///
/// </summary>
public class CryptoParams
{
public byte[] Mi { get; set; } = new byte[P25Defines.P25_MI_LENGTH];
public byte AlgId { get; set; } = P25Defines.P25_ALGO_UNENCRYPT;
public ushort KeyId { get; set; }
}
}

@ -188,6 +188,19 @@
</LinearGradientBrush>
</Button.Background>
</Button>
<Button VerticalContentAlignment="Center" Content="Call Hist" HorizontalAlignment="Left" Margin="743,0,0,0" VerticalAlignment="Center" Height="46" Width="44" Click="CallHist_Click" BorderBrush="#FFC1C1C1" BorderThickness="1,1,1,1" FontSize="10" FontFamily="Arial" Grid.Row="1">
<Button.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="2"/>
</Style>
</Button.Resources>
<Button.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFF0F0F0" Offset="0.485"/>
<GradientStop Color="#FFDBDBDB" Offset="0.517"/>
</LinearGradientBrush>
</Button.Background>
</Button>
<Image HorizontalAlignment="Left" Margin="514,3,0,3" Grid.Row="1" Width="38" Source="/Assets/page.png" IsHitTestVisible="False"/>
<Button Content="" VerticalContentAlignment="Bottom" HorizontalAlignment="Left" Margin="556,0,0,0" VerticalAlignment="Center" Height="46" Width="44" Click="AudioSettings_Click" BorderBrush="#FFC1C1C1" BorderThickness="1,1,1,1" Grid.Row="1" FontSize="10" FontFamily="Arial">
<Button.Resources>

@ -49,6 +49,7 @@ using fnecore.P25.LC.TSBK;
using WebSocketSharp;
using NWaves.Signals;
using static WhackerLinkConsoleV2.P25Crypto;
using static WhackerLinkLib.Models.Radio.Codeplug;
namespace WhackerLinkConsoleV2
{
@ -73,6 +74,8 @@ namespace WhackerLinkConsoleV2
private ChannelBox playbackChannelBox;
CallHistoryWindow callHistoryWindow = new CallHistoryWindow();
public static string PLAYBACKTG = "LOCPLAYBACK";
public static string PLAYBACKSYS = "LOCPLAYBACKSYS";
public static string PLAYBACKCHNAME = "PLAYBACK";
@ -93,7 +96,7 @@ namespace WhackerLinkConsoleV2
public MainWindow()
{
#if DEBUG
#if !DEBUG
ConsoleNative.ShowConsole();
#endif
InitializeComponent();
@ -276,8 +279,6 @@ namespace WhackerLinkConsoleV2
{
_fneSystemManager.AddFneSystem(system.Name, system, this);
systemStatuses.Add(system.Name, new SlotStatus());
PeerSystem peer = _fneSystemManager.GetFneSystem(system.Name);
peer.peer.PeerConnected += (sender, response) =>
@ -325,6 +326,8 @@ namespace WhackerLinkConsoleV2
//channelBox.crypter.AddKey(channel.GetKeyId(), channel.GetAlgoId(), channel.GetEncryptionKey());
systemStatuses.Add(channel.Name, new SlotStatus());
if (_settingsManager.ChannelPositions.TryGetValue(channel.Name, out var position))
{
Canvas.SetLeft(channelBox, position.X);
@ -1615,6 +1618,8 @@ namespace WhackerLinkConsoleV2
/// </summary>
private void P25EncodeAudioFrame(byte[] pcm, PeerSystem handler, ChannelBox channel, Codeplug.Channel cpgChannel, Codeplug.System system)
{
bool encryptCall = true; // TODO: make this dynamic somewhere?
if (channel.p25N > 17)
channel.p25N = 0;
if (channel.p25N == 0)
@ -1682,6 +1687,35 @@ namespace WhackerLinkConsoleV2
}
// Log.Logger.Debug($"IMBE {FneUtils.HexDump(imbe)}");
if (encryptCall && cpgChannel.GetAlgoId() != 0 && cpgChannel.GetKeyId() != 0)
{
// initial HDU MI
if (channel.p25N == 0)
{
if (channel.mi.All(b => b == 0))
{
Random random = new Random();
for (int i = 0; i < P25Defines.P25_MI_LENGTH; i++)
{
channel.mi[i] = (byte)random.Next(0x00, 0x100);
}
channel.crypter.Prepare(cpgChannel.GetAlgoId(), cpgChannel.GetKeyId(), ProtocolType.P25Phase1, channel.mi);
}
}
// crypto time
channel.crypter.Process(imbe, channel.p25N < 9U ? P25Crypto.FrameType.LDU1 : P25Crypto.FrameType.LDU2, 0);
// last block of LDU2, prepare a new MI
if (channel.p25N == 17U)
{
P25Crypto.CycleP25Lfsr(channel.mi);
channel.crypter.Prepare(cpgChannel.GetAlgoId(), cpgChannel.GetKeyId(), ProtocolType.P25Phase1, channel.mi);
}
}
// fill the LDU buffers appropriately
switch (channel.p25N)
{
@ -1767,7 +1801,7 @@ namespace WhackerLinkConsoleV2
//Console.WriteLine($"({channel.SystemName}) P25D: Traffic *VOICE FRAME * PEER {handler.PeerId} SRC_ID {srcId} TGID {dstId} [STREAM ID {channel.txStreamId}]");
byte[] payload = new byte[200];
handler.CreateNewP25MessageHdr((byte)P25DUID.LDU1, callData, ref payload);
handler.CreateNewP25MessageHdr((byte)P25DUID.LDU1, callData, ref payload, cpgChannel.GetAlgoId(), cpgChannel.GetKeyId(), channel.mi);
handler.CreateP25LDU1Message(channel.netLDU1, ref payload, srcId, dstId);
peer.SendMaster(new Tuple<byte, byte>(Constants.NET_FUNC_PROTOCOL, Constants.NET_PROTOCOL_SUBFUNC_P25), payload, pktSeq, channel.txStreamId);
@ -1785,8 +1819,8 @@ namespace WhackerLinkConsoleV2
//Console.WriteLine($"({channel.SystemName}) P25D: Traffic *VOICE FRAME * PEER {handler.PeerId} SRC_ID {srcId} TGID {dstId} [STREAM ID {channel.txStreamId}]");
byte[] payload = new byte[200];
handler.CreateNewP25MessageHdr((byte)P25DUID.LDU2, callData, ref payload);
handler.CreateP25LDU2Message(channel.netLDU2, ref payload);
handler.CreateNewP25MessageHdr((byte)P25DUID.LDU2, callData, ref payload, cpgChannel.GetAlgoId(), cpgChannel.GetKeyId(), channel.mi);
handler.CreateP25LDU2Message(channel.netLDU2, ref payload, new CryptoParams { AlgId = cpgChannel.GetAlgoId(), KeyId = cpgChannel.GetKeyId(), Mi = channel.mi });
peer.SendMaster(new Tuple<byte, byte>(Constants.NET_FUNC_PROTOCOL, Constants.NET_PROTOCOL_SUBFUNC_P25), payload, pktSeq, channel.txStreamId);
}
@ -1988,6 +2022,7 @@ namespace WhackerLinkConsoleV2
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
bool isEmergency = false;
bool encrypted = false;
if (!system.IsDvm)
continue;
@ -2000,9 +2035,9 @@ namespace WhackerLinkConsoleV2
if (cpgChannel.Tgid != e.DstId.ToString())
continue;
if (!systemStatuses.ContainsKey(system.Name))
if (!systemStatuses.ContainsKey(cpgChannel.Name))
{
systemStatuses[system.Name] = new SlotStatus();
systemStatuses[cpgChannel.Name] = new SlotStatus();
}
if (channel.decoder == null)
@ -2010,7 +2045,7 @@ namespace WhackerLinkConsoleV2
channel.decoder = new MBEDecoder(MBE_MODE.IMBE_88BIT);
}
SlotStatus slot = systemStatuses[system.Name];
SlotStatus slot = systemStatuses[cpgChannel.Name];
// if this is an LDU1 see if this is the first LDU with HDU encryption data
if (e.DUID == P25DUID.LDU1)
@ -2025,6 +2060,8 @@ namespace WhackerLinkConsoleV2
Array.Copy(e.Data, 184, channel.mi, 0, P25Defines.P25_MI_LENGTH);
channel.crypter.Prepare(channel.algId, channel.kId, P25Crypto.ProtocolType.P25Phase1, channel.mi);
encrypted = true;
}
}
@ -2035,6 +2072,9 @@ namespace WhackerLinkConsoleV2
slot.RxStart = pktTime;
// Console.WriteLine($"({system.Name}) P25D: Traffic *CALL START * PEER {e.PeerId} SRC_ID {e.SrcId} TGID {e.DstId} [STREAM ID {e.StreamId}]");
callHistoryWindow.AddCall(cpgChannel.Name, (int)e.SrcId, (int)e.DstId);
callHistoryWindow.ChannelKeyed(cpgChannel.Name, (int)e.SrcId, encrypted);
string alias = string.Empty;
try
@ -2061,6 +2101,7 @@ namespace WhackerLinkConsoleV2
TimeSpan callDuration = pktTime - slot.RxStart;
// Console.WriteLine($"({system.Name}) P25D: Traffic *CALL END * PEER {e.PeerId} SRC_ID {e.SrcId} TGID {e.DstId} DUR {callDuration} [STREAM ID {e.StreamId}]");
channel.Background = (Brush)new BrushConverter().ConvertFrom("#FF0B004B");
callHistoryWindow.ChannelUnkeyed(cpgChannel.Name, (int)e.SrcId);
return;
}
@ -2205,5 +2246,10 @@ namespace WhackerLinkConsoleV2
}
});
}
private void CallHist_Click(object sender, RoutedEventArgs e)
{
callHistoryWindow.Show();
}
}
}

Loading…
Cancel
Save

Powered by TurnKey Linux.