Browse Source

commit: sender signature

Local 5 years ago
parent
commit
344a1d8d1e

+ 2 - 0
README.md

@@ -1,3 +1,5 @@
 # ud-punching
 
 UDP hole punching
+
+Key size is fixed 4096

+ 5 - 5
UdPunching.Common/ExchangeMessage.cs

@@ -7,11 +7,11 @@ namespace UdPunching
 {
     public class ExchangeMessage
     {
-        // [0....] Id
-        // [1....] Section count
-        // [2....] Section Type
-        // [3...N] Section Content
-        // [N+1..] Next Section Type
+        // [0.....] Id
+        // [1.....] Section count
+        // [2.....] Section Type
+        // [3....N] Section Content
+        // [N....M] Next Section Type
 
         public ExchangeMessageId Id { get; set; }
 

+ 63 - 19
UdPunching.Common/TransferCodec.cs

@@ -3,44 +3,87 @@ using System.IO;
 using System.IO.Compression;
 using System.Linq;
 using System.Security.Cryptography;
+using System.Security.Policy;
 
 namespace UdPunching
 {
     public static class TransferCodec
     {
-        // [0.....15] GUID PeerId
-        // [16....17] Encrypted data length prefix (LeInt16)
-        // [18...END] Encrypted data content
+        //Transfer format
+        // [0...............15] GUID Sender peer id
+        // [ gz if compressable
+        //  [15....15+ChunkSize] data encrypt by receiverPublicKey
+        //  [16+ChunkSize...END] signature of [encrypted data] of senderPrivateKey
+        // ]
 
-        public static Guid ReadId(byte[] recvData)
+        private const int RsaKeySize = 4096;
+        private const int BlockSize = RsaKeySize / 8;
+
+        private static readonly MD5CryptoServiceProvider Md5 = new MD5CryptoServiceProvider();
+
+        public static RSACng LoadKey(string path)
         {
-            return new Guid(recvData.Take(16).ToArray());
+            var rsa = new RSACng();
+            rsa.FromXmlString(File.ReadAllText(path));
+            if (RsaKeySize == rsa.KeySize) return rsa;
+            rsa.Dispose();
+            throw new ArgumentException("invalid key size");
         }
 
-        public static byte[] DecodeData(RSACryptoServiceProvider localKey, byte[] recvData)
+        public static Guid ReadId(byte[] receivedData)
         {
-            recvData.ReadLeInt16(out var len, 16);
-            var raw = recvData.Skip(18).Take(len).ToArray();
-            var decompressed = Decompress(raw);
-            var decodeData = localKey.Decrypt(decompressed, false);
-            return decodeData;
+            return new Guid(receivedData.Take(16).ToArray());
         }
 
-        public static byte[] Encode(RSACryptoServiceProvider receiverKey, Guid id, byte[] data)
+        public static byte[] Encode(RSACng senderPrivateKey, RSACng receiverPublicKey, Guid senderId, byte[] data)
         {
-            var encryptedBytes = receiverKey.Encrypt(data, false);
-            var compressedData = Compress(encryptedBytes);
+            byte[] pass1;
+            byte[] signature;
+            {
+                pass1 = receiverPublicKey.Encrypt(data, RSAEncryptionPadding.OaepSHA512);// encode
+                var hash = Md5.ComputeHash(pass1);
+                using (RSA rsaCng = new RSACng())
+                {
+                    rsaCng.ImportParameters(senderPrivateKey.ExportParameters(true));
+                    signature = rsaCng.SignHash(hash, HashAlgorithmName.MD5, RSASignaturePadding.Pkcs1); // signature
+                }
+            }
+
+            var toCompress = new byte[BlockSize * 2];
+            pass1.WriteTo(toCompress);
+            signature.WriteTo(toCompress, BlockSize);
+            var compressed = Compress(toCompress);
 
-            var buf = new byte[16 + 2 + compressedData.Length];
+            var buf = new byte[16 + compressed.Length];
 
             var ptr = -1;
-            ptr += id.WriteTo(buf, ++ptr); // 16
-            ptr += compressedData.Length.WriteLeInt16To(buf, ++ptr); // 2
-            compressedData.WriteTo(buf, ++ptr);
+            ptr += senderId.WriteTo(buf, ++ptr);
+            ptr += compressed.WriteTo(buf, ++ptr);
 
             return buf;
         }
 
+        public static byte[] DecodeData(RSACng receiverPrivateKey, RSACng senderPublicKey, byte[] data)
+        {
+            var pass2 = Decompress(data, 16); // skip sender id
+
+            pass2.ReadBytes(BlockSize, out var enc);
+            pass2.ReadBytes(BlockSize, out var sig, BlockSize);
+
+            using (RSA rsaCng = new RSACng())
+            {
+                rsaCng.ImportParameters(senderPublicKey.ExportParameters(false));
+                var hash = Md5.ComputeHash(enc);
+                if (false == rsaCng.VerifyHash(hash, sig, HashAlgorithmName.MD5, RSASignaturePadding.Pkcs1))
+                {
+                    throw new InvalidDataException("Signature verify fail");
+                }
+            }
+
+            var decodeData = receiverPrivateKey.Decrypt(enc, RSAEncryptionPadding.OaepSHA512); //decode
+            return decodeData;
+        }
+
         private static byte[] Compress(byte[] data)
         {
             using (var rawIn = new MemoryStream(data))
@@ -65,11 +108,12 @@ namespace UdPunching
             }
         }
 
-        private static byte[] Decompress(byte[] data)
+        private static byte[] Decompress(byte[] data, int startFrom = 0)
         {
             using (var rawIn = new MemoryStream(data))
             using (var finalOut = new MemoryStream())
             {
+                rawIn.Position = startFrom;
                 if (rawIn.ReadByte() == 1)
                 {
                     using (var gZipStream = new GZipStream(rawIn, CompressionMode.Decompress))

+ 23 - 3
UdPunching.Common/UtilMethods.cs

@@ -31,9 +31,9 @@ namespace UdPunching
             throw new FormatException("parse fail");
         }
 
-        public static void SendExchangeMessageTo(this Socket socket, Guid senderId, ExchangeMessage message, EndPoint to, RSACryptoServiceProvider receiverKey)
+        public static void SendExchangeMessageTo(this Socket socket, EndPoint to, RSACng senderPrivateKey, RSACng receiverPublicKey, Guid senderId, ExchangeMessage message)
         {
-            var bytes = TransferCodec.Encode(receiverKey, senderId, message.ToBytes());
+            var bytes = TransferCodec.Encode(senderPrivateKey, receiverPublicKey, senderId, message.ToBytes());
             socket.SendTo(bytes, to);
         }
 
@@ -45,7 +45,7 @@ namespace UdPunching
             return ipAddress.ToString().PadRight(15, ' ');
         }
 
-        //--------- ToBytes Series -------------
+        //--------- Bytes Series -------------
 
         public static byte[] ToLeInt16Bytes(this int value)
         {
@@ -69,6 +69,19 @@ namespace UdPunching
 
         public static byte[] ToAsciiBytes(this string str) => Encoding.ASCII.GetBytes(str);
 
+        public static byte[] TakeBytes(this byte[] me, int count)
+        {
+            if (me.Length < count) throw new ArgumentOutOfRangeException(nameof(count));
+            var bytes = new byte[count];
+            Buffer.BlockCopy(me, 0, bytes, 0, count);
+            return bytes;
+        }
+
+        public static byte[] GetTransferredBytes(this SocketAsyncEventArgs sae)
+        {
+            return sae.Buffer.TakeBytes(sae.BytesTransferred);
+        }
+
         //--------- Read Series -------------
 
         public static int ReadLeInt16(this byte[] buf, out int value, int index = 0)
@@ -118,6 +131,13 @@ namespace UdPunching
             return count;
         }
 
+        public static int ReadBytes(this byte[] buf, int count, out byte[] read, int index = 0)
+        {
+            read = new byte[count];
+            Buffer.BlockCopy(buf, index, read, 0, count);
+            return count;
+        }
+
         //--------- Write...To Series -------------
 
         public static int WriteLeInt16To(this int value, byte[] buf, int index = 0)

+ 22 - 33
UdPunching.ExampleW/ExampleForm.cs

@@ -16,15 +16,14 @@ namespace UdPunching.ExampleW
 
         private static readonly IPEndPoint AnyEndPoint = new IPEndPoint(IPAddress.Any, 0);
 
-        private readonly IReadOnlyDictionary<Guid, RSACryptoServiceProvider> _peerKeyRegister;
+        private readonly IReadOnlyDictionary<Guid, RSACng> _peerPublicKeyRegistry;
 
         private IPEndPoint _serverEndPoint;
-        private RSACryptoServiceProvider _serverKey;
+        private RSACng _serverPublicKey;
         private SocketAsyncEventArgs _saeReceive;
 
         private Guid _localId;
-        private RSACryptoServiceProvider _localKey;
-
+        private RSACng _localPrivateKey;
         private Socket _localSocket;
         private IPEndPoint _localPublicEndPoint;
 
@@ -37,15 +36,10 @@ namespace UdPunching.ExampleW
         {
             InitializeComponent();
 
-            _peerKeyRegister = Directory.GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PeerPublicKey"))
+            _peerPublicKeyRegistry = Directory.GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PeerPublicKey"))
                 .ToDictionary(
                     s => new Guid(Path.GetFileNameWithoutExtension(s)),
-                    p =>
-                    {
-                        var rsa = new RSACryptoServiceProvider();
-                        rsa.FromXmlString(File.ReadAllText(p));
-                        return rsa;
-                    }
+                    TransferCodec.LoadKey
                 );
         }
 
@@ -53,8 +47,8 @@ namespace UdPunching.ExampleW
 
         private void ExampleForm_Shown(object sender, EventArgs e)
         {
-            _serverKey = new RSACryptoServiceProvider();
-            _serverKey.FromXmlString(File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ServerPublicKey.txt")));
+            _serverPublicKey = new RSACng();
+            _serverPublicKey.FromXmlString(File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ServerPublicKey.txt")));
 
             var privateKeys = Directory
                 .GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PrivateKey"))
@@ -63,7 +57,7 @@ namespace UdPunching.ExampleW
 
             PeerKetyDropDown.DataSource = privateKeys;
 
-            PeerToKnockDropDown.DataSource = _peerKeyRegister.Keys.ToArray();
+            PeerToKnockDropDown.DataSource = _peerPublicKeyRegistry.Keys.ToArray();
         }
 
         private void StartButton_Click(object sender, EventArgs e)
@@ -73,13 +67,8 @@ namespace UdPunching.ExampleW
             _serverEndPoint = ServerIEndPointTextBox.Text.ParseToIpEndPointV4();
 
             _localId = new Guid(PeerKetyDropDown.Text);
-            _localKey = new RSACryptoServiceProvider();
             var peerPrivateKeyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PrivateKey", PeerKetyDropDown.Text + ".txt");
-            _localKey.FromXmlString(
-                File.ReadAllText(
-                    peerPrivateKeyPath
-                )
-            );
+            _localPrivateKey = TransferCodec.LoadKey(peerPrivateKeyPath);
 
             _localSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
             _localSocket.Bind(AnyEndPoint);
@@ -108,11 +97,11 @@ namespace UdPunching.ExampleW
 
             _localSocket?.Dispose();
             _saeReceive?.Dispose();
-            _localKey?.Dispose();
+            _localPrivateKey?.Dispose();
 
             _localSocket = null;
             _saeReceive = null;
-            _localKey = null;
+            _localPrivateKey = null;
 
             PeerKetyDropDown.Enabled = true;
             StartButton.Enabled = true;
@@ -127,7 +116,7 @@ namespace UdPunching.ExampleW
         {
             _keepAliveMsg.TimeStamp = DateTime.Now;
             _keepAliveMsg.WriteToBuffer(_keepAliveBuf);
-            _localSocket.SendExchangeMessageTo(_localId, _keepAliveMsg, _serverEndPoint, _serverKey);
+            _localSocket.SendExchangeMessageTo(_serverEndPoint, _localPrivateKey, _serverPublicKey, _localId, _keepAliveMsg);
         }
 
         private void KnockButton_Click(object sender, EventArgs e)
@@ -144,7 +133,7 @@ namespace UdPunching.ExampleW
                 PeerId = idToKnock
             };
 
-            _localSocket.SendExchangeMessageTo(_localId, msg, _serverEndPoint, _serverKey);
+            _localSocket.SendExchangeMessageTo(_serverEndPoint, _localPrivateKey, _serverPublicKey, _localId, msg);
         }
 
         private void SendButton_Click(object sender, EventArgs e)
@@ -152,7 +141,7 @@ namespace UdPunching.ExampleW
             var to = SendToEndPointTextBox.Text.ParseToIpEndPointV4();
             var sendMsg = new ExchangeMessage(ExchangeMessageId.DataTransfer);
             sendMsg.PayloadBytes = Encoding.UTF8.GetBytes(SendContentTextBox.Text);
-            var sendBytes = TransferCodec.Encode(_peerKeyRegister[new Guid(PeerToKnockDropDown.Text)], _localId, sendMsg.ToBytes());
+            var sendBytes = TransferCodec.Encode(_localPrivateKey, _peerPublicKeyRegistry[new Guid(PeerToKnockDropDown.Text)], _localId, sendMsg.ToBytes());
             var sent = _localSocket.SendTo(sendBytes, to);
         }
 
@@ -166,7 +155,7 @@ namespace UdPunching.ExampleW
                 if (BuildInPeerId.Invalid == peerId) throw new InvalidDataException("SERVER ERROR: FAILURE");
                 if (Guid.Empty != peerId) throw new InvalidDataException("SERVER ERROR: INVALID SERVER PEER ID");
 
-                var msgData = TransferCodec.DecodeData(_localKey, _saeReceive.Buffer);
+                var msgData = TransferCodec.DecodeData(_localPrivateKey, _serverPublicKey, _saeReceive.Buffer);
                 var msg = new ExchangeMessage(msgData);
 
                 switch (msg.Id)
@@ -192,7 +181,7 @@ namespace UdPunching.ExampleW
 
                         ExchangeMessage msgReply;
 
-                        if (false == _peerKeyRegister.TryGetValue(msg.PeerId.Value, out var peerKey))
+                        if (false == _peerPublicKeyRegistry.TryGetValue(msg.PeerId.Value, out var peerKey))
                         {
                             Log($"DENIED {msg.Id}: peer id {msg.PeerId.Value}");
                             msgReply = new ExchangeMessage(ExchangeMessageId.PeerKnockDenied);
@@ -204,10 +193,10 @@ namespace UdPunching.ExampleW
 
                             Log($"SENDING CONNECTION REQ to {msg.PeerId} @ {msg.PeerEndPoint}");
                             var connMsg = new ExchangeMessage(ExchangeMessageId.PeerKnockConnectionReq);
-                            _localSocket.SendExchangeMessageTo(_localId, connMsg, msg.PeerEndPoint, peerKey);
+                            _localSocket.SendExchangeMessageTo(msg.PeerEndPoint, _localPrivateKey, peerKey, _localId, connMsg);
                         }
 
-                        _localSocket.SendExchangeMessageTo(_localId, msgReply, _serverEndPoint, _serverKey);
+                        _localSocket.SendExchangeMessageTo(_serverEndPoint, _localPrivateKey, _serverPublicKey, _localId, msgReply);
                         break;
 
                     case ExchangeMessageId.PeerKnockAckRelay:
@@ -227,7 +216,7 @@ namespace UdPunching.ExampleW
                         {
                             Log($"SENDING CONNECTION REQ to {msg.PeerId} @ {msg.PeerEndPoint}");
                             var connMsg = new ExchangeMessage(ExchangeMessageId.PeerKnockConnectionReq);
-                            _localSocket.SendExchangeMessageTo(_localId, connMsg, msg.PeerEndPoint, _peerKeyRegister[msg.PeerId.Value]);
+                            _localSocket.SendExchangeMessageTo(msg.PeerEndPoint, _localPrivateKey, _peerPublicKeyRegistry[msg.PeerId.Value], _localId, connMsg);
                         }
                         break;
 
@@ -253,12 +242,12 @@ namespace UdPunching.ExampleW
             }
             else
             {
-                if (BuildInPeerId.Invalid == peerId || BuildInPeerId.Server == peerId || false == _peerKeyRegister.TryGetValue(peerId, out var peerKey))
+                if (BuildInPeerId.Invalid == peerId || BuildInPeerId.Server == peerId || false == _peerPublicKeyRegistry.TryGetValue(peerId, out var peerPublicKey))
                 {
                     throw new InvalidDataException("PEER ERROR: INVALID PEER ID");
                 }
 
-                var msgData = TransferCodec.DecodeData(_localKey, _saeReceive.Buffer);
+                var msgData = TransferCodec.DecodeData(_localPrivateKey, peerPublicKey, _saeReceive.Buffer);
                 var msg = new ExchangeMessage(msgData);
 
                 var reply = new ExchangeMessage { TimeStamp = DateTime.Now };
@@ -294,7 +283,7 @@ namespace UdPunching.ExampleW
                     }
                 }
 
-                _localSocket.SendExchangeMessageTo(_localId, reply, _saeReceive.RemoteEndPoint, peerKey);
+                _localSocket.SendExchangeMessageTo(_saeReceive.RemoteEndPoint, _localPrivateKey, peerPublicKey, _localId, reply);
             }
         }
 

File diff suppressed because it is too large
+ 0 - 1
UdPunching.Serv/PeerPublicKey/26531b6b-1fa1-4c70-8a1e-7c1b579742a5.txt


File diff suppressed because it is too large
+ 0 - 1
UdPunching.Serv/PeerPublicKey/ab9ae069-1bed-4fce-8e6a-feabb8c519a7.txt


+ 16 - 32
UdPunching.Serv/ServProgram.cs

@@ -15,8 +15,8 @@ namespace UdPunching.Serv
     {
         private static readonly ConcurrentDictionary<Guid, OnlineSession> OnlineSessions = new ConcurrentDictionary<Guid, OnlineSession>();
 
-        private static RSACryptoServiceProvider _serverKey;
-        private static IReadOnlyDictionary<Guid, RSACryptoServiceProvider> _peerKeyRegister;
+        private static RSACng _serverPrivateKey;
+        private static IReadOnlyDictionary<Guid, RSACng> _peerPublicKeyRegistry;
 
         private static volatile int _packetSeq;
         private static Socket _svrSocket;
@@ -30,9 +30,9 @@ namespace UdPunching.Serv
             ++_packetSeq;
 
             var peerId = TransferCodec.ReadId(sae.Buffer);
-            _peerKeyRegister.TryGetValue(peerId, out var peerKey);
+            _peerPublicKeyRegistry.TryGetValue(peerId, out var peerPublicKey);
 
-            if (null == peerKey)
+            if (null == peerPublicKey)
             {
                 Console.WriteLine($"pacet #{_packetSeq} peer id {peerId} no registered");
                 _svrSocket.SendTo(BuildInPeerId.Invalid.ToByteArray(), sae.RemoteEndPoint);
@@ -43,7 +43,7 @@ namespace UdPunching.Serv
 
             try
             {
-                var msgData = TransferCodec.DecodeData(_serverKey, sae.Buffer);
+                var msgData = TransferCodec.DecodeData(_serverPrivateKey, peerPublicKey, sae.GetTransferredBytes());
                 requestMessage = new ExchangeMessage(msgData);
             }
             catch (Exception exception)
@@ -102,7 +102,7 @@ namespace UdPunching.Serv
                         case ExchangeMessageId.PeerKnockReq:
 
                             if (false == requestMessage.PeerId.HasValue
-                                || false == _peerKeyRegister.TryGetValue(requestMessage.PeerId.Value, out var knockKey)
+                                || false == _peerPublicKeyRegistry.TryGetValue(requestMessage.PeerId.Value, out var knockPublicKey)
                                 || false == OnlineSessions.TryGetValue(requestMessage.PeerId.Value, out var knockSession)
                             )
                             {
@@ -120,7 +120,7 @@ namespace UdPunching.Serv
                                 PeerEndPoint = (IPEndPoint)sae.RemoteEndPoint,
                             };
 
-                            var reqRelayBytes = TransferCodec.Encode(knockKey, BuildInPeerId.Server, reqRelayMessage.ToBytes());
+                            var reqRelayBytes = TransferCodec.Encode(_serverPrivateKey, knockPublicKey, BuildInPeerId.Server, reqRelayMessage.ToBytes());
                             _svrSocket.SendTo(reqRelayBytes, knockSession.RemoteEndPoint);
 
                             responseMessage.Id = ExchangeMessageId.PeerKnockReqRelayed;
@@ -128,9 +128,8 @@ namespace UdPunching.Serv
                             break;
 
                         case ExchangeMessageId.PeerKnockAck:
-
                             if (false == requestMessage.PeerId.HasValue
-                                || false == _peerKeyRegister.TryGetValue(requestMessage.PeerId.Value, out var knockAckKey)
+                                || false == _peerPublicKeyRegistry.TryGetValue(requestMessage.PeerId.Value, out var knockAckKey)
                                 || false == OnlineSessions.TryGetValue(requestMessage.PeerId.Value, out var knockAckSession)
                             )
                             {
@@ -148,7 +147,7 @@ namespace UdPunching.Serv
                                 PeerEndPoint = (IPEndPoint)sae.RemoteEndPoint,
                             };
 
-                            _svrSocket.SendExchangeMessageTo(BuildInPeerId.Server, ackRelayMessage, knockAckSession.RemoteEndPoint, knockAckKey);
+                            _svrSocket.SendExchangeMessageTo(knockAckSession.RemoteEndPoint, _serverPrivateKey, knockAckKey, BuildInPeerId.Server, ackRelayMessage);
 
                             responseMessage.Id = ExchangeMessageId.PeerKnockAckRelayed;
                             responseMessage.PeerId = requestMessage.PeerId;
@@ -156,25 +155,16 @@ namespace UdPunching.Serv
                             break;
 
                         case ExchangeMessageId.PeerKnockDenied:
-
+                            //TODO: Relay PeerKnockDenied
                             break;
 
-                        //case ExchangeMessageId.PeerKnockConnectionReq:
-                        //    break;
-
-                        //case ExchangeMessageId.PeerKnockConnectionAck:
-                        //    break;
-
-                        //case ExchangeMessageId.ErrSessionNoCreated:
-                        //    break;
-
                         default:
                             throw new ArgumentOutOfRangeException();
                     }
                 }
             }
-            
-            _svrSocket.SendExchangeMessageTo(BuildInPeerId.Server, responseMessage, sae.RemoteEndPoint, peerKey);
+
+            _svrSocket.SendExchangeMessageTo(sae.RemoteEndPoint, _serverPrivateKey, peerPublicKey, BuildInPeerId.Server, responseMessage);
         }
 
         private static void DeadLoopTimeOutKiller()
@@ -217,21 +207,15 @@ namespace UdPunching.Serv
         private static void Main()
         {
             Console.WriteLine("Init...");
-            
+
             //TODO: Crash log
 
-            _serverKey = new RSACryptoServiceProvider();
-            _serverKey.FromXmlString(File.ReadAllText("ServerPrivateKey.txt"));
+            _serverPrivateKey = TransferCodec.LoadKey("ServerPrivateKey.txt");
 
-            _peerKeyRegister = Directory.GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PeerPublicKey"))
+            _peerPublicKeyRegistry = Directory.GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PeerPublicKey"))
                 .ToDictionary(
                     s => new Guid(Path.GetFileNameWithoutExtension(s)),
-                    p =>
-                    {
-                        var rsa = new RSACryptoServiceProvider();
-                        rsa.FromXmlString(File.ReadAllText(p));
-                        return rsa;
-                    }
+                    TransferCodec.LoadKey
                 );
 
             _svrSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

+ 4 - 2
UdPunching.Serv/UdPunching.Serv.csproj

@@ -72,10 +72,12 @@
     </ProjectReference>
   </ItemGroup>
   <ItemGroup>
-    <Content Include="PeerPublicKey\26531b6b-1fa1-4c70-8a1e-7c1b579742a5.txt">
+    <Content Include="..\UdPunching.ExampleW\PeerPublicKey\26531b6b-1fa1-4c70-8a1e-7c1b579742a5.txt">
+      <Link>PeerPublicKey\26531b6b-1fa1-4c70-8a1e-7c1b579742a5.txt</Link>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="PeerPublicKey\ab9ae069-1bed-4fce-8e6a-feabb8c519a7.txt">
+    <Content Include="..\UdPunching.ExampleW\PeerPublicKey\ab9ae069-1bed-4fce-8e6a-feabb8c519a7.txt">
+      <Link>PeerPublicKey\ab9ae069-1bed-4fce-8e6a-feabb8c519a7.txt</Link>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     <Content Include="ServerPublicKey.txt" />