diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..eb40b61 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,22 @@ +version: 2 +jobs: + build: + docker: + - image: microsoft/dotnet:2-sdk + + working_directory: ~/repo + + steps: + - checkout + + - run: + name: Get Examples + command: git clone --depth 1 https://github.com/cose-wg/Examples Regressions + + - run: + name: Unit Tests + command: | + ls + dotnet build --framework=netcoreapp2.0 CoAP.Std10.sln + dotnet test --framework=netcoreapp2.0 CoAP.Std10.sln + diff --git a/CoAP.Example/CoAP.Client/CoAP.Client.Std10.csproj b/CoAP.Example/CoAP.Client/CoAP.Client.Std10.csproj index 1b78690..6264cfe 100644 --- a/CoAP.Example/CoAP.Client/CoAP.Client.Std10.csproj +++ b/CoAP.Example/CoAP.Client/CoAP.Client.Std10.csproj @@ -1,85 +1,27 @@ - - + + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {30223FF5-4DCB-45B8-9B06-61EB9FBF9FA8} Exe - Properties - Com.AugustCellars.CoAP.Examples - CoAPClient - v4.6.2 - 512 - - true + netcoreapp2.0;net462 - - true - full - false - obj\Debug\Std10 - bin\Debug\Std10\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - obj\Release\Std10 - bin\Release\Std10\ - TRACE - prompt - 4 - - - pdbonly - true - obj\Deploy\Std10 - bin\Deploy\Std10\ + + TRACE - prompt - 4 + - - - - - - + + + - - {45db1e45-4831-4e4a-bb1e-ae92eea182e3} - CoAP.Std10 - + + + - - - - - - - - ..\..\packages\PeterO.Cbor.3.0.3\lib\netstandard1.0\CBOR.dll - - - ..\..\packages\PeterO.Numbers.1.0.2\lib\netstandard1.0\Numbers.dll - - - ..\..\packages\Com.AugustCellars.COSE.1.3.0\lib\netstandard1.0\COSE.dll - + - - - \ No newline at end of file + + diff --git a/CoAP.Example/CoAP.Client/ExampleClient.cs b/CoAP.Example/CoAP.Client/ExampleClient.cs index 898f1f9..6212e84 100644 --- a/CoAP.Example/CoAP.Client/ExampleClient.cs +++ b/CoAP.Example/CoAP.Client/ExampleClient.cs @@ -277,6 +277,7 @@ private static SecurityContextSet LoadContextSet(string fileName) if (usage == "oscoap") { SecurityContext ctx = SecurityContext.DeriveContext( key[CoseKeyParameterKeys.Octet_k].GetByteString(), + null, key[CBORObject.FromObject("RecipID")].GetByteString(), key[CBORObject.FromObject("SenderID")].GetByteString(), null, key[CoseKeyKeys.Algorithm]); @@ -284,7 +285,10 @@ private static SecurityContextSet LoadContextSet(string fileName) break; } else if (usage == "oscoap-group") { - SecurityContext ctx = SecurityContext.DeriveGroupContext(key[CoseKeyParameterKeys.Octet_k].GetByteString(), key[CBORObject.FromObject(2)].GetByteString(), key[CBORObject.FromObject("SenderID")].GetByteString(), null, null, key[CoseKeyKeys.Algorithm]); + SecurityContext ctx = SecurityContext.DeriveGroupContext( + key[CoseKeyParameterKeys.Octet_k].GetByteString(), key[CBORObject.FromObject(2)].GetByteString(), key[CBORObject.FromObject("SenderID")].GetByteString(), + null, null, + null, null, null, key[CoseKeyKeys.Algorithm]); foreach (CBORObject recipient in key[CBORObject.FromObject("recipients")].Values) { ctx.AddRecipient(recipient[CBORObject.FromObject("RecipID")].GetByteString(), new OneKey( recipient[CBORObject.FromObject("sign")])); } diff --git a/CoAP.Example/CoAP.Client/packages.config b/CoAP.Example/CoAP.Client/packages.config index 370df3f..fa45386 100644 --- a/CoAP.Example/CoAP.Client/packages.config +++ b/CoAP.Example/CoAP.Client/packages.config @@ -1,11 +1,11 @@  - + - + diff --git a/CoAP.Example/CoAP.Server/CoAP.Server.Std10.csproj b/CoAP.Example/CoAP.Server/CoAP.Server.Std10.csproj index ca92fba..6264cfe 100644 --- a/CoAP.Example/CoAP.Server/CoAP.Server.Std10.csproj +++ b/CoAP.Example/CoAP.Server/CoAP.Server.Std10.csproj @@ -1,79 +1,27 @@ - - + + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {05CC2E5E-B663-4560-844C-C8E52ED1C9D1} Exe - Properties - Com.AugustCellars.CoAP.Examples - CoAPServer - v4.6.2 - 512 - - true + netcoreapp2.0;net462 - - true - full - false - obj\Debug\Std10 - bin\Debug\Std10\ - TRACE;DEBUG - prompt - 4 - - - pdbonly - true - obj\Release\Std10 - bin\Release\Std10\ - TRACE - prompt - 4 - - - pdbonly - true - obj\Deploy\Std10 - bin\Deploy\Std10\ + + TRACE - prompt - 4 + - - - - - - - - - - - - - - - {45DB1E45-4831-4E4A-BB1E-AE92EEA182E3} - CoAP.Std10 - + + + - + + + - + - - - \ No newline at end of file + + diff --git a/CoAP.Example/CoAP.Server/app.config b/CoAP.Example/CoAP.Server/app.config index 2ffb337..d09514f 100644 --- a/CoAP.Example/CoAP.Server/app.config +++ b/CoAP.Example/CoAP.Server/app.config @@ -12,7 +12,7 @@ - + diff --git a/CoAP.NET/Channel/DataReceivedEventArgs.cs b/CoAP.NET/Channel/DataReceivedEventArgs.cs index a7291a1..0d5bb7f 100644 --- a/CoAP.NET/Channel/DataReceivedEventArgs.cs +++ b/CoAP.NET/Channel/DataReceivedEventArgs.cs @@ -20,11 +20,12 @@ public class DataReceivedEventArgs : EventArgs { /// /// - public DataReceivedEventArgs(Byte[] data, System.Net.EndPoint endPoint, ISession session) + public DataReceivedEventArgs(Byte[] data, System.Net.EndPoint endPoint, System.Net.EndPoint endPointLocal, ISession session) { Data = data; EndPoint = endPoint; Session = session; + LocalEndPoint = endPointLocal; } /// @@ -37,6 +38,11 @@ public DataReceivedEventArgs(Byte[] data, System.Net.EndPoint endPoint, ISession /// public System.Net.EndPoint EndPoint { get; } + /// + /// Get the where the data was received. + /// + public System.Net.EndPoint LocalEndPoint { get; } + /// /// Gets the communication session for the message. /// diff --git a/CoAP.NET/Channel/IChannel.cs b/CoAP.NET/Channel/IChannel.cs index b1ea711..0c51e70 100644 --- a/CoAP.NET/Channel/IChannel.cs +++ b/CoAP.NET/Channel/IChannel.cs @@ -10,6 +10,7 @@ */ using System; +using System.Net; namespace Com.AugustCellars.CoAP.Channel { @@ -26,6 +27,14 @@ public interface IChannel : IDisposable /// Occurs when some bytes are received in this channel. /// event EventHandler DataReceived; +#if !NETSTANDARD1_3 + /// + /// Add a multicast address to the channel + /// + /// address to add + /// true if added + bool AddMulticastAddress(IPEndPoint ep); +#endif /// /// Starts this channel. /// diff --git a/CoAP.NET/Channel/UDPChannel.cs b/CoAP.NET/Channel/UDPChannel.cs index 26a8a5e..e3b99cd 100644 --- a/CoAP.NET/Channel/UDPChannel.cs +++ b/CoAP.NET/Channel/UDPChannel.cs @@ -11,7 +11,10 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq.Expressions; using System.Net; +using System.Net.NetworkInformation; using System.Net.Sockets; using System.Runtime.InteropServices; using Com.AugustCellars.CoAP.Log; @@ -28,10 +31,11 @@ public partial class UDPChannel : IChannel, ISession /// public const Int32 DefaultReceivePacketSize = 4096; - private Int32 _port; - private System.Net.EndPoint _localEP; - private UDPSocket _socket; - private UDPSocket _socketBackup; + private readonly Int32 _port; +// private readonly System.Net.EndPoint _localEP; + private SocketSet _unicast = new SocketSet(); +// private UDPSocket _socket; +// private UDPSocket _socketBackup; private Int32 _running; private Int32 _writing; private readonly ConcurrentQueue _sendingQueue = new ConcurrentQueue(); @@ -64,7 +68,7 @@ public UDPChannel(Int32 port) /// public UDPChannel(System.Net.EndPoint localEP) { - _localEP = localEP; + _unicast._localEP = (IPEndPoint) localEP; } /// @@ -72,9 +76,9 @@ public System.Net.EndPoint LocalEndPoint { get { - return _socket == null - ? (_localEP ?? new IPEndPoint(IPAddress.IPv6Any, _port)) - : _socket.Socket.LocalEndPoint; + return _unicast._socket == null + ? (_unicast._localEP ?? new IPEndPoint(IPAddress.IPv6Any, _port)) + : _unicast._socket.Socket.LocalEndPoint; } } @@ -104,6 +108,23 @@ public System.Net.EndPoint LocalEndPoint /// public int MaxSendSize { get; set; } +#if !NETSTANDARD1_3 + private readonly List _listMultiCastEndpoints = new List(); + + /// + public bool AddMulticastAddress(IPEndPoint ep) // IPAddress ep, int port) + { + SocketSet s = new SocketSet() { + _localEP = ep + }; + _listMultiCastEndpoints.Add(s); + if (_running == 1) { + + } + return true; + } +#endif + /// public void Start() { @@ -114,68 +135,185 @@ public void Start() #if LOG_UDP_CHANNEL _Log.Debug("Start"); #endif + try { + + StartSocket(_unicast); + +#if !NETSTANDARD1_3 + foreach (SocketSet s in _listMultiCastEndpoints) { + StartMulticastSocket(s); + } +#endif + } + catch (Exception) { + _running = 0; + throw; + } + } - if (_localEP == null) { + private void StartSocket(SocketSet info) + { + if (info._localEP == null) { try { - _socket = SetupUDPSocket(AddressFamily.InterNetworkV6, ReceivePacketSize + 1); // +1 to check for > ReceivePacketSize + info._socket = SetupUDPSocket(AddressFamily.InterNetworkV6, ReceivePacketSize + 1); // +1 to check for > ReceivePacketSize } catch (SocketException e) { if (e.SocketErrorCode == SocketError.AddressFamilyNotSupported) { - _socket = null; + info._socket = null; } else { throw; } } - if (_socket == null) { + if (info._socket == null) { // IPv6 is not supported, use IPv4 instead - _socket = SetupUDPSocket(AddressFamily.InterNetwork, ReceivePacketSize + 1); - _socket.Socket.Bind(new IPEndPoint(IPAddress.Any, _port)); + info._socket = SetupUDPSocket(AddressFamily.InterNetwork, ReceivePacketSize + 1); + info._socket.Socket.Bind(new IPEndPoint(IPAddress.Any, _port)); } else { try { // Enable IPv4-mapped IPv6 addresses to accept both IPv6 and IPv4 connections in a same socket. - _socket.Socket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, 0); + info._socket.Socket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, 0); } catch { #if LOG_UDP_CHANNEL _Log.Debug("Create backup socket"); #endif // IPv4-mapped address seems not to be supported, set up a separated socket of IPv4. - _socketBackup = SetupUDPSocket(AddressFamily.InterNetwork, ReceivePacketSize + 1); + info._socketBackup = SetupUDPSocket(AddressFamily.InterNetwork, ReceivePacketSize + 1); } - _socket.Socket.Bind(new IPEndPoint(IPAddress.IPv6Any, _port)); - if (_socketBackup != null) { - _socketBackup.Socket.Bind(new IPEndPoint(IPAddress.Any, _port)); + info._socket.Socket.Bind(new IPEndPoint(IPAddress.IPv6Any, _port)); + if (info._socketBackup != null) { + info._socketBackup.Socket.Bind(new IPEndPoint(IPAddress.Any, _port)); } } } else { - _socket = SetupUDPSocket(_localEP.AddressFamily, ReceivePacketSize + 1); - _socket.Socket.Bind(_localEP); + info._socket = SetupUDPSocket(info._localEP.AddressFamily, ReceivePacketSize + 1); + + info._socket.Socket.Bind(info._localEP); } + if (ReceiveBufferSize > 0) { - _socket.Socket.ReceiveBufferSize = ReceiveBufferSize; - if (_socketBackup != null) { - _socketBackup.Socket.ReceiveBufferSize = ReceiveBufferSize; + info._socket.Socket.ReceiveBufferSize = ReceiveBufferSize; + if (info._socketBackup != null) { + info._socketBackup.Socket.ReceiveBufferSize = ReceiveBufferSize; } } if (SendBufferSize > 0) { - _socket.Socket.SendBufferSize = SendBufferSize; - if (_socketBackup != null) { - _socketBackup.Socket.SendBufferSize = SendBufferSize; + info._socket.Socket.SendBufferSize = SendBufferSize; + if (info._socketBackup != null) { + info._socketBackup.Socket.SendBufferSize = SendBufferSize; } } - BeginReceive(); + BeginReceive(info); } - /// - public void Stop() +#if !NETSTANDARD1_3 + private void StartMulticastSocket(SocketSet info) + { + if (info._localEP.Address.IsIPv6Multicast) { + try { + info._socket = SetupUDPSocket(AddressFamily.InterNetworkV6, ReceivePacketSize + 1); + info._socket.Socket.Bind(new IPEndPoint(IPAddress.IPv6Any, _port)); + + NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces(); + foreach (NetworkInterface adapter in nics) { + if (!adapter.SupportsMulticast) continue; + if (adapter.OperationalStatus != OperationalStatus.Up) continue; + if (adapter.Supports(NetworkInterfaceComponent.IPv6)) { + IPInterfaceProperties properties = adapter.GetIPProperties(); + + foreach (UnicastIPAddressInformation ip in adapter.GetIPProperties().UnicastAddresses) { + if (ip.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6) { + IPv6InterfaceProperties v6ip = adapter.GetIPProperties().GetIPv6Properties(); + IPv6MulticastOption mc = new IPv6MulticastOption(info._localEP.Address, v6ip.Index); + try { + info._socket.Socket.SetSocketOption(SocketOptionLevel.IPv6, + SocketOptionName.AddMembership, + mc); + } + catch (SocketException e) { +#if LOG_UDP_CHANNEL + _Log.Info( + m => m( + $"Start Multicast: Address {info._localEP.Address} had an exception ${e.ToString()}")); +#endif + } + + break; + } + } + } + } + } + catch (SocketException e) { +#if LOG_UDP_CHANNEL + _Log.Info( + m => m($"Start Multicast: Address {info._localEP.Address} had an exception ${e.ToString()}")); + throw; +#endif + } + } + else { + try { + info._socket = SetupUDPSocket(AddressFamily.InterNetwork, ReceivePacketSize + 1); + info._socket.Socket.Bind(new IPEndPoint(IPAddress.Any, _port)); + + NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces(); + + + foreach (NetworkInterface adapter in nics) { + if (!adapter.SupportsMulticast) continue; + if (adapter.OperationalStatus != OperationalStatus.Up) continue; + if (adapter.Supports(NetworkInterfaceComponent.IPv4)) { + IPInterfaceProperties properties = adapter.GetIPProperties(); + + foreach (UnicastIPAddressInformation ip in adapter.GetIPProperties().UnicastAddresses) { + if (ip.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) { + MulticastOption mc = new MulticastOption(info._localEP.Address, ip.Address); + info._socket.Socket.SetSocketOption(SocketOptionLevel.IP, + SocketOptionName.AddMembership, + mc); + } + } + } + } + } + catch (SocketException e) { +#if LOG_UDP_CHANNEL + _Log.Info(m => m($"Start Multicast: Address {info._localEP.Address} had an exception ${e.ToString()}")); +#endif + throw; + } + } + + + if (ReceiveBufferSize > 0) { + info._socket.Socket.ReceiveBufferSize = ReceiveBufferSize; + if (info._socketBackup != null) { + info._socketBackup.Socket.ReceiveBufferSize = ReceiveBufferSize; + } + } + + if (SendBufferSize > 0) { + info._socket.Socket.SendBufferSize = SendBufferSize; + if (info._socketBackup != null) { + info._socketBackup.Socket.SendBufferSize = SendBufferSize; + } + } + + BeginReceive(info); + } +#endif + + /// + public void Stop() { #if LOG_UDP_CHANNEL _Log.Debug("Stop"); @@ -184,14 +322,14 @@ public void Stop() return; } - if (_socket != null) { - _socket.Dispose(); - _socket = null; + if (_unicast._socket != null) { + _unicast._socket.Dispose(); + _unicast._socket = null; } - if (_socketBackup != null) { - _socketBackup.Dispose(); - _socketBackup = null; + if (_unicast._socketBackup != null) { + _unicast._socketBackup.Dispose(); + _unicast._socketBackup = null; } } @@ -242,16 +380,16 @@ public ISession GetSession(System.Net.EndPoint ep) return this; } - private void BeginReceive() + private void BeginReceive(SocketSet info) { #if LOG_UDP_CHANNEL _Log.Debug(m => m("BeginRecieve: _running={0}", _running)); #endif if (_running > 0) { - BeginReceive(_socket); + BeginReceive(info._socket); - if (_socketBackup != null) { - BeginReceive(_socketBackup); + if (info._socketBackup != null) { + BeginReceive(info._socketBackup); } } } @@ -273,7 +411,15 @@ private void EndReceive(UDPSocket socket, Byte[] buffer, Int32 offset, Int32 cou } } - FireDataReceived(bytes, ep); + System.Net.EndPoint epLocal = socket.Socket.LocalEndPoint; + if (ep.AddressFamily == AddressFamily.InterNetworkV6) { + IPEndPoint ipLocal = (IPEndPoint) ep; + if (IPAddressExtensions.IsIPv4MappedToIPv6(ipLocal.Address)) { + epLocal = new IPEndPoint(IPAddressExtensions.MapToIPv4(ipLocal.Address), ipLocal.Port); + } + } + + FireDataReceived(bytes, ep, epLocal); } #if LOG_UDP_CHANNEL @@ -290,14 +436,14 @@ private void EndReceive(UDPSocket socket, Exception ex) BeginReceive(socket); } - private void FireDataReceived(Byte[] data, System.Net.EndPoint ep) + private void FireDataReceived(Byte[] data, System.Net.EndPoint ep, System.Net.EndPoint epLocal) { #if LOG_UDP_CHANNEL _Log.Debug(m => m("FireDataReceived: data length={0}", data.Length)); #endif EventHandler h = DataReceived; if (h != null) { - h(this, new DataReceivedEventArgs(data, ep, this)); + h(this, new DataReceivedEventArgs(data, ep, epLocal, this)); } } @@ -313,15 +459,15 @@ private void BeginSend() return; } - UDPSocket socket = _socket; + UDPSocket socket = _unicast._socket; IPEndPoint remoteEP = (IPEndPoint)raw.EndPoint; if (remoteEP.AddressFamily == AddressFamily.InterNetwork) { - if (_socketBackup != null) { + if (_unicast._socketBackup != null) { // use the separated socket of IPv4 to deal with IPv4 conversions. - socket = _socketBackup; + socket = _unicast._socketBackup; } - else if (_socket.Socket.AddressFamily == AddressFamily.InterNetworkV6) { + else if (_unicast._socket.Socket.AddressFamily == AddressFamily.InterNetworkV6) { remoteEP = new IPEndPoint(IPAddressExtensions.MapToIPv6(remoteEP.Address), remoteEP.Port); } } @@ -384,6 +530,13 @@ class RawData public System.Net.EndPoint EndPoint; } + class SocketSet + { + public IPEndPoint _localEP; + public UDPSocket _socket; + public UDPSocket _socketBackup; + } + /// public event EventHandler SessionEvent; diff --git a/CoAP.NET/CoAP.Std10.csproj b/CoAP.NET/CoAP.Std10.csproj index 9d078dc..8b2ed2b 100644 --- a/CoAP.NET/CoAP.Std10.csproj +++ b/CoAP.NET/CoAP.Std10.csproj @@ -4,11 +4,11 @@ - netstandard1.3;net462 + netcoreapp2.0;net462;netstandard2.0 Com.AugustCellars.CoAP Com.AugustCellars.CoAP - 1.2.0.0 - 1.2.0.0 + 1.3.0.0 + 1.3.0.0 Jim Schaad An implementation of the CBOR Object Signing and Encryption standards. false @@ -19,6 +19,10 @@ This project is built on the CoAP.NET project of smeshlink which in turn is based on Californium. As this project did not seem to be maintained any more and I wanted a version in order to test the newer items that are coming out of the IETF CORE working group, I have captured it and started exanding it. This project is NOT intended to be used for commercial purposes. It is intented only for research and verification work. +1.3 + - Remove NetStandard 1.3 in favor of 2.0 and add NetCore 2.0 + - Put in multcast support + - Primative version of CoRAL 1.2.0.0 - Update net 4.5 file to use latest CBOR - Switch to use NetStandard 1.3 rather than Net 4.5 @@ -76,7 +80,7 @@ This project is NOT intended to be used for commercial purposes. It is intented - TRACE;INCLUDE_OSCOAP;OSCOAP_COMPRESS;LOG_UDP_CHANNEL + TRACE;INCLUDE_OSCOAP;OSCOAP_COMPRESS @@ -87,15 +91,15 @@ This project is NOT intended to be used for commercial purposes. It is intented AugustCellarsStrongKey.snk true - obj\Deploy - bin\Deploy\ + obj\std10\Deploy + bin\std10\Deploy\ DEBUG $(DefineConstants);DEBUG - obj\Debug - bin\Debug\ + obj\std10\Debug + bin\std10\Debug\ @@ -116,6 +120,12 @@ This project is NOT intended to be used for commercial purposes. It is intented + + + + + + @@ -130,6 +140,7 @@ This project is NOT intended to be used for commercial purposes. It is intented + @@ -205,6 +216,7 @@ This project is NOT intended to be used for commercial purposes. It is intented + @@ -214,7 +226,7 @@ This project is NOT intended to be used for commercial purposes. It is intented - + diff --git a/CoAP.NET/CoapClient.cs b/CoAP.NET/CoapClient.cs index ff1bab1..0703eec 100644 --- a/CoAP.NET/CoapClient.cs +++ b/CoAP.NET/CoapClient.cs @@ -132,7 +132,14 @@ public CoapClient(Uri uri, ICoapConfig config) /// /// OSCOAP context to use for the message /// - public OSCOAP.SecurityContext OscoapContext { get; set; } + [ObsoleteAttribute("Use OscoreContext instead")] + public OSCOAP.SecurityContext OscoapContext + { + get => OscoreContext; + set => OscoreContext = value; + } + + public OSCOAP.SecurityContext OscoreContext { get; set; } /// /// Let the client use Confirmable requests. @@ -253,11 +260,13 @@ public IEnumerable Discover(string uriPath, String query, int mediaType case MediaType.ApplicationLinkFormat: return LinkFormat.Parse(links.PayloadString); +#if false case MediaType.ApplicationLinkFormatCbor: return LinkFormat.ParseCbor(links.Payload); case MediaType.ApplicationLinkFormatJson: return LinkFormat.ParseJson(links.PayloadString); +#endif default: return _EmptyLinks; @@ -650,7 +659,7 @@ protected Request Prepare(Request request, IEndPoint endpoint) { request.Type = _type; request.URI = Uri; - request.OscoapContext = OscoapContext; + request.OscoreContext = OscoreContext; if (UriPath != null) { request.UriPath = UriPath; diff --git a/CoAP.NET/Coral/CoralBase.cs b/CoAP.NET/Coral/CoralBase.cs new file mode 100644 index 0000000..7f18c0a --- /dev/null +++ b/CoAP.NET/Coral/CoralBase.cs @@ -0,0 +1,30 @@ +using PeterO.Cbor; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Com.AugustCellars.CoAP.Util; + +namespace Com.AugustCellars.CoAP.Coral +{ + public class CoralBase : CoralItem + { + public Uri Uri { get; } + public CoralBase(Uri baseUri) + { + Uri = baseUri; + } + + public override CBORObject EncodeToCBORObject(CoralDictionary dictionary) + { + CBORObject node = CBORObject.NewArray(); + + node.Add(1); + node.Add(Ciri.ToCbor(Uri)); + + return node; + } + + } +} diff --git a/CoAP.NET/Coral/CoralBody.cs b/CoAP.NET/Coral/CoralBody.cs new file mode 100644 index 0000000..60a3b1d --- /dev/null +++ b/CoAP.NET/Coral/CoralBody.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel.Design.Serialization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using PeterO.Cbor; + +namespace Com.AugustCellars.CoAP.Coral +{ + public class CoralBody : IEnumerable + { + private readonly List _items = new List(); + + public CoralBody() + { + } + + + public CoralBody(CBORObject node, CoralDictionary dictionary) + { + if (node.Type != CBORType.Array) { + throw new ArgumentException("Invalid node type"); + } + + foreach (CBORObject child in node.Values) { + if (node.Type != CBORType.Array) + { + throw new ArgumentException("Invalid node type"); + } + + switch (node[0].AsInt32()) { + case 1: + _items.Add(new CoralLink(node, dictionary)); + break; + + default: + throw new ArgumentException("Unrecognized CoRAL node type"); + } + } + } + + public int Length => _items.Count; + + public CoralBody Add(CoralItem item) + { + _items.Add(item); + return this; + } + + static CoralBody DecodeFromBytes(byte[] encoded, CoralDictionary dictionary = null) + { + CBORObject obj = CBORObject.DecodeFromBytes(encoded); + if (dictionary == null) { + dictionary = CoralDictionary.Default; + } + return new CoralBody(obj, dictionary); + } + + public byte[] EncodeToBytes(CoralDictionary dictionary = null) + { + return EncodeToCBORObject(dictionary).EncodeToBytes(); + } + + public CBORObject EncodeToCBORObject(CoralDictionary dictionary = null) + { + CBORObject root = CBORObject.NewArray(); + if (dictionary == null) { + dictionary = CoralDictionary.Default; + } + + foreach (CoralItem item in _items) { + root.Add(item.EncodeToCBORObject(dictionary)); + } + + return root; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _items.GetEnumerator(); + } + } +} diff --git a/CoAP.NET/Coral/CoralDictionary.cs b/CoAP.NET/Coral/CoralDictionary.cs new file mode 100644 index 0000000..3f02d43 --- /dev/null +++ b/CoAP.NET/Coral/CoralDictionary.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Org.BouncyCastle.Asn1.Crmf; +using PeterO.Cbor; + +namespace Com.AugustCellars.CoAP.Coral +{ + public class CoralDictionary : System.Collections.IEnumerable + { + public const int DictionaryTag = 99999; + + public static CoralDictionary Default = new CoralDictionary() { + {0, "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"}, + {1, "http://www.iana.org/assignments/relation/item>"}, + {2, "http://www.iana.org/assignments/relation/collection"}, + {3, "http://coreapps.org/collections#create"}, + {4, "http://coreapps.org/base#update"}, + {5, "http://coreapps.org/collections#delete"}, + {6, "http://coreapps.org/base#search"}, + {7, "http://coreapps.org/coap#accept"}, + {8, "http://coreapps.org/coap#type"}, + {9, "http://coreapps.org/base#lang"}, + {10, "http://coreapps.org/coap#method"} + }; + + private readonly Dictionary _dictionary = new Dictionary(); + + public CoralDictionary() + { + } + + public CoralDictionary Add(int key, string value) + { + _dictionary.Add(key, value); + return this; + } + + public IEnumerator GetEnumerator() + { + return ((IEnumerable) _dictionary).GetEnumerator(); + } + + public CBORObject Lookup(CBORObject value) + { + foreach (KeyValuePair o in _dictionary) { + if (value.Equals(CBORObject.FromObject(o.Value))) { + CBORObject newValue = CBORObject.FromObject(o.Key); + if (value.Type == CBORType.Number) { + newValue = CBORObject.FromObjectAndTag(newValue, DictionaryTag); + } + + return newValue; + } + } + + return value; + } + + public CBORObject Lookup(string value) + { + foreach (KeyValuePair o in _dictionary) { + if (value.Equals(o.Value)) { + return CBORObject.FromObject(o.Key); + } + } + + return CBORObject.FromObject(value); + } + + public CBORObject Reverse(CBORObject value) + { + if (value.Type != CBORType.Number) { + return value; + } + + if (value.HasTag(DictionaryTag) && value.MostOuterTag.ToInt32Unchecked() == DictionaryTag) { + return value.UntagOne(); + } + + if (!_dictionary.ContainsKey(value.AsInt32())) { + return value; + } + + CBORObject result = CBORObject.FromObject(_dictionary[value.AsInt32()]); + + if (result.Type == CBORType.Number) { + return value; + } + + return result; + } + } +} diff --git a/CoAP.NET/Coral/CoralForm.cs b/CoAP.NET/Coral/CoralForm.cs new file mode 100644 index 0000000..420d57f --- /dev/null +++ b/CoAP.NET/Coral/CoralForm.cs @@ -0,0 +1,42 @@ +using PeterO.Cbor; + +namespace Com.AugustCellars.CoAP.Coral +{ + public class CoralForm : CoralItem + { + private readonly string _operationType; + private readonly string _target; + /// + /// Child body + /// + public CoralBody Body { get; } = new CoralBody(); + + public CoralForm(string formRef, string target) + { + _operationType = formRef; + _target = target; + } + + public CoralForm(string formRef, string target, CoralBody body) + { + _operationType = formRef; + _target = target; + Body = body; + } + + public override CBORObject EncodeToCBORObject(CoralDictionary dictionary) + { + CBORObject node = CBORObject.NewArray(); + + node.Add(3); + node.Add(dictionary.Lookup(_operationType)); + node.Add(dictionary.Lookup(_target)); + if (Body.Length > 0) + { + node.Add(Body.EncodeToCBORObject(dictionary)); + } + + return node; + } + } +} diff --git a/CoAP.NET/Coral/CoralItem.cs b/CoAP.NET/Coral/CoralItem.cs new file mode 100644 index 0000000..c87a742 --- /dev/null +++ b/CoAP.NET/Coral/CoralItem.cs @@ -0,0 +1,15 @@ +using PeterO.Cbor; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Com.AugustCellars.CoAP.Coral +{ + public abstract class CoralItem + { + public abstract CBORObject EncodeToCBORObject(CoralDictionary dictionary); + } + +} diff --git a/CoAP.NET/Coral/CoralLink.cs b/CoAP.NET/Coral/CoralLink.cs new file mode 100644 index 0000000..90c9c83 --- /dev/null +++ b/CoAP.NET/Coral/CoralLink.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Org.BouncyCastle.Asn1.X509; +using PeterO.Cbor; + +namespace Com.AugustCellars.CoAP.Coral +{ + public class CoralLink : CoralItem + { + /// + /// Link relation Type - a text string conforming to IRI syntax + /// + public string RelationType { get; } + /// + /// Target of the link + /// + public CBORObject Target { get; set; } + /// + /// Child body + /// + public CoralBody Body { get; } + + public CoralLink(string relation, CBORObject target) + { + RelationType = relation; + Target = target; + } + + public CoralLink(string relation, CBORObject target, CoralBody body) + { + RelationType = relation; + Target = target; + Body = body; + } + + public CoralLink(CBORObject node, CoralDictionary dictionary) + { + if (node[0].AsInt32() != 2) { + throw new ArgumentException("Not an encoded CoRAL link"); + } + + RelationType = dictionary.Reverse(node[1]).AsString(); + Target = dictionary.Reverse(node[2]); + if (node.Count == 4) { + Body = new CoralBody(node[3], dictionary); + } + } + + public override CBORObject EncodeToCBORObject(CoralDictionary dictionary) + { + CBORObject node = CBORObject.NewArray(); + + node.Add(2); + node.Add(dictionary.Lookup(RelationType)); + node.Add(dictionary.Lookup(Target)); + if (Body != null) { + node.Add(Body.EncodeToCBORObject(dictionary)); + } + + return node; + } + } +} diff --git a/CoAP.NET/DTLS/DTLSChannel.cs b/CoAP.NET/DTLS/DTLSChannel.cs index f46e94e..221cbf2 100644 --- a/CoAP.NET/DTLS/DTLSChannel.cs +++ b/CoAP.NET/DTLS/DTLSChannel.cs @@ -25,19 +25,20 @@ public class DTLSChannel : IChannel private Int32 _receivePacketSize; private readonly int _port; private UDPChannel _udpChannel; - private KeySet _serverKeys; + private TlsKeyPairSet _serverKeys; private KeySet _userKeys; - + private KeySet _cwtTrustRoots; - public DTLSChannel(KeySet serverKeys, KeySet userKeys) : this(serverKeys, userKeys, 0) + public DTLSChannel(TlsKeyPairSet serverKeys, KeySet userKeys) : this(serverKeys, userKeys, 0) { } - public DTLSChannel(KeySet serverKeys, KeySet userKeys, Int32 port) + public DTLSChannel(TlsKeyPairSet serverKeys, KeySet userKeys, Int32 port, KeySet cwtTrustRoots = null) { _port = port; _userKeys = userKeys; _serverKeys = serverKeys; + _cwtTrustRoots = cwtTrustRoots; } /// @@ -46,7 +47,7 @@ public DTLSChannel(KeySet serverKeys, KeySet userKeys, Int32 port) /// /// /// - public DTLSChannel(KeySet serverKeys, KeySet userKeys, System.Net.EndPoint ep) + public DTLSChannel(TlsKeyPairSet serverKeys, KeySet userKeys, System.Net.EndPoint ep) { _localEP = ep; _userKeys = userKeys; @@ -89,6 +90,12 @@ public Int32 ReceivePacketSize { public EventHandler TlsEventHandler; + /// + public bool AddMulticastAddress(IPEndPoint ep) + { + return false; + } + public void Start() { if (System.Threading.Interlocked.CompareExchange(ref _running, 1, 0) > 0) { @@ -165,7 +172,7 @@ public ISession GetSession(System.Net.EndPoint ep) // No session - create a new one. - session = new DTLSSession(ipEndPoint, DataReceived, _serverKeys, _userKeys); + session = new DTLSSession(ipEndPoint, DataReceived, _serverKeys, _userKeys, _cwtTrustRoots); AddSession(session); session.TlsEventHandler += MyTlsEventHandler; @@ -195,7 +202,7 @@ public void Send(byte[] data, ISession sessionReceive, System.Net.EndPoint ep) DTLSSession session = FindSession(ipEP); if (session == null) { - session = new DTLSSession(ipEP, DataReceived, _serverKeys, _userKeys); + session = new DTLSSession(ipEP, DataReceived, _serverKeys, _userKeys, _cwtTrustRoots); session.TlsEventHandler += MyTlsEventHandler; AddSession(session); session.Connect(_udpChannel); @@ -223,7 +230,7 @@ private void ReceiveData(Object sender, DataReceivedEventArgs e) } } - DTLSSession sessionNew = new DTLSSession((IPEndPoint) e.EndPoint, DataReceived, _serverKeys, _userKeys); + DTLSSession sessionNew = new DTLSSession((IPEndPoint) e.EndPoint, DataReceived, _serverKeys, _userKeys, _cwtTrustRoots); sessionNew.TlsEventHandler = MyTlsEventHandler; _sessionList.Add(sessionNew); new Thread(() => Accept(sessionNew, e.Data)).Start(); diff --git a/CoAP.NET/DTLS/DTLSClient.cs b/CoAP.NET/DTLS/DTLSClient.cs index 46744e5..153a933 100644 --- a/CoAP.NET/DTLS/DTLSClient.cs +++ b/CoAP.NET/DTLS/DTLSClient.cs @@ -1,8 +1,11 @@ using System; using System.Collections; - +using System.IO; using System.Threading.Tasks; using Com.AugustCellars.COSE; +#if SUPPORT_TLS_CWT +using Com.AugustCellars.WebToken; +#endif using Org.BouncyCastle.Asn1.Nist; using Org.BouncyCastle.Asn1.Sec; using Org.BouncyCastle.Crypto.Tls; @@ -28,6 +31,7 @@ class DtlsClient : DefaultTlsClient private TlsSession _mSession; public EventHandler TlsEventHandler; public OneKey _rawPublicKey; + private TlsKeyPair _tlsKeyPair; internal DtlsClient(TlsSession session, TlsPskIdentity pskIdentity) { @@ -41,6 +45,16 @@ internal DtlsClient(TlsSession session, OneKey userKey) _rawPublicKey = userKey; } +#if SUPPORT_TLS_CWT + public KeySet CwtTrustKeySet { get; set; } + internal DtlsClient(TlsSession session, TlsKeyPair tlsKey, KeySet cwtTrustKeys) + { + _mSession = session; + _tlsKeyPair = tlsKey; + CwtTrustKeySet = cwtTrustKeys; + } +#endif + private void OnTlsEvent(Object o, TlsEvent e) { EventHandler handler = TlsEventHandler; @@ -63,6 +77,14 @@ public override int[] GetCipherSuites() CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 }; } +#if SUPPORT_TLS_CWT + else if (_tlsKeyPair != null && _tlsKeyPair.CertType == CertificateType.CwtPublicKey) { + i = new int[] { + CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, + CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 + }; + } +#endif else { i = new int[] { CipherSuite.TLS_PSK_WITH_AES_128_CCM_8, @@ -81,6 +103,33 @@ public override int[] GetCipherSuites() return e.IntValues; } +#if SUPPORT_TLS_CWT + public override AbstractCertificate ParseServerCertificate(short certificateType, Stream io) + { + switch (certificateType) { + case CertificateType.CwtPublicKey: + try { + CwtPublicKey cwtPub = CwtPublicKey.Parse(io); + + CWT cwtServer = CWT.Decode(cwtPub.EncodedCwt(), CwtTrustKeySet, CwtTrustKeySet); + + AsymmetricKeyParameter pubKey = cwtServer.Cnf.Key.AsPublicKey(); + + SubjectPublicKeyInfo spi = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pubKey); + cwtPub.SetSubjectPublicKeyInfo(spi); + + return cwtPub; + } + catch { + return null; + } + + default: + return null; + } + + } +#endif public override IDictionary GetClientExtensions() { @@ -93,14 +142,21 @@ public override IDictionary GetClientExtensions() /* * NOTE: If you are copying test code, do not blindly set these extensions in your own client. */ - // TlsExtensionsUtilities.AddMaxFragmentLengthExtension(clientExtensions, MaxFragmentLength.pow2_9); - // TlsExtensionsUtilities.AddPaddingExtension(clientExtensions, mContext.SecureRandom.Next(16)); - // TlsExtensionsUtilities.AddTruncatedHMacExtension(clientExtensions); + // TlsExtensionsUtilities.AddMaxFragmentLengthExtension(clientExtensions, MaxFragmentLength.pow2_9); + // TlsExtensionsUtilities.AddPaddingExtension(clientExtensions, mContext.SecureRandom.Next(16)); + // TlsExtensionsUtilities.AddTruncatedHMacExtension(clientExtensions); if (_rawPublicKey != null) { - TlsExtensionsUtilities.AddClientCertificateTypeExtensionClient(clientExtensions, new byte[]{2}); - TlsExtensionsUtilities.AddServerCertificateTypeExtensionClient(clientExtensions, new byte[]{2}); + TlsExtensionsUtilities.AddClientCertificateTypeExtensionClient(clientExtensions, new byte[] {2}); + TlsExtensionsUtilities.AddServerCertificateTypeExtensionClient(clientExtensions, new byte[] {2}); + } + +#if SUPPORT_TLS_CWT + if (_tlsKeyPair != null && _tlsKeyPair.CertType == CertificateType.CwtPublicKey) { + TlsExtensionsUtilities.AddClientCertificateTypeExtensionClient(clientExtensions, new byte[] {254}); + TlsExtensionsUtilities.AddServerCertificateTypeExtensionClient(clientExtensions, new byte[] {254}); } +#endif } TlsEvent e = new TlsEvent(TlsEvent.EventCode.GetExtensions) { @@ -118,9 +174,20 @@ public override IDictionary GetClientExtensions() public override TlsAuthentication GetAuthentication() { - MyTlsAuthentication auth = new MyTlsAuthentication(mContext, _rawPublicKey); - auth.TlsEventHandler += MyTlsEventHandler; - return auth; + if (_rawPublicKey != null) { + MyTlsAuthentication auth = new MyTlsAuthentication(mContext, _rawPublicKey); + auth.TlsEventHandler += MyTlsEventHandler; + return auth; + } +#if SUPPORT_TLS_CWT + else if (_tlsKeyPair != null && _tlsKeyPair.CertType == CertificateType.CwtPublicKey) { + MyTlsAuthentication auth = new MyTlsAuthentication(mContext, _tlsKeyPair, CwtTrustKeySet); + auth.TlsEventHandler += MyTlsEventHandler; + return auth; + } +#endif + + throw new Exception("ICE"); } private void MyTlsEventHandler(object sender, TlsEvent tlsEvent) @@ -160,6 +227,7 @@ public override TlsKeyExchange GetKeyExchange() } } + private BigInteger ConvertBigNum(CBORObject cbor) { byte[] rgb = cbor.GetByteString(); @@ -193,6 +261,26 @@ protected TlsSignerCredentials GetECDsaSignerCredentials() } #endif +#if SUPPORT_TLS_CWT + if (_tlsKeyPair != null && _tlsKeyPair.CertType == CertificateType.CwtPublicKey) { + OneKey k = _tlsKeyPair.PublicCwt.Cnf.Key; + if (k.HasKeyType((int)COSE.GeneralValuesInt.KeyType_EC2) && + k.HasAlgorithm(COSE.AlgorithmValues.ECDSA_256)) { + + X9ECParameters p = k.GetCurve(); + ECDomainParameters parameters = new ECDomainParameters(p.Curve, p.G, p.N, p.H); + ECPrivateKeyParameters privKey = new ECPrivateKeyParameters("ECDSA", ConvertBigNum(k[CoseKeyParameterKeys.EC_D]), parameters); + + ECPoint point = k.GetPoint(); + ECPublicKeyParameters param = new ECPublicKeyParameters(point, parameters); + + SubjectPublicKeyInfo spi = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(param); + + return new DefaultTlsSignerCredentials(mContext, new CwtPublicKey(_tlsKeyPair.PublicCwt.EncodeToBytes()), privKey, new SignatureAndHashAlgorithm(HashAlgorithm.sha256, SignatureAlgorithm.ecdsa)); + } + } +#endif + TlsEvent e = new TlsEvent(TlsEvent.EventCode.SignCredentials) { CipherSuite = KeyExchangeAlgorithm.ECDH_ECDSA }; @@ -224,6 +312,10 @@ internal class MyTlsAuthentication public EventHandler TlsEventHandler; private OneKey _rawPublicKey; private KeySet _serverKeys; +#if SUPPORT_TLS_CWT + private TlsKeyPair TlsKey { get; set; } + public KeySet CwtTrustKeySet { get; set; } +#endif internal MyTlsAuthentication(TlsContext context, OneKey rawPublicKey) { @@ -231,15 +323,34 @@ internal MyTlsAuthentication(TlsContext context, OneKey rawPublicKey) _rawPublicKey = rawPublicKey; } +#if SUPPORT_TLS_CWT + internal MyTlsAuthentication(TlsContext context, TlsKeyPair cwt, KeySet trustKeys) + { + this._mContext = context; + TlsKey = cwt; + CwtTrustKeySet = trustKeys; + } +#endif + public OneKey AuthenticationKey { get; private set; } -#if SUPPORT_RPK +#if SUPPORT_RPK || SUPPORT_TLS_CWT + + protected virtual AbstractCertificate ParseServerCertificate2(short serverCertificateType, Stream stm) + { + return null; + } public virtual void NotifyServerCertificate(AbstractCertificate serverCertificate) { if (serverCertificate is RawPublicKey) { GetRpkKey((RawPublicKey)serverCertificate); } +#if SUPPORT_TLS_CWT + else if (serverCertificate is CwtPublicKey) { + GetCwtKey((CwtPublicKey) serverCertificate); + } +#endif else { TlsEvent e = new TlsEvent(TlsEvent.EventCode.ServerCertificate) { Certificate = serverCertificate @@ -301,6 +412,8 @@ public void GetRpkKey(RawPublicKey rpk) if (!ev.Processed) { // throw new TlsFatalAlert(AlertDescription.certificate_unknown); } + + AuthenticationKey = ev.KeyValue; } else { // throw new TlsFatalAlert(AlertDescription.certificate_unknown); @@ -310,6 +423,40 @@ public void GetRpkKey(RawPublicKey rpk) #endif +#if SUPPORT_TLS_CWT + public void GetCwtKey(CwtPublicKey rpk) + { + try { + CWT cwt = CWT.Decode(rpk.EncodedCwt(), CwtTrustKeySet, CwtTrustKeySet); + + AsymmetricKeyParameter pub = cwt.Cnf.Key.AsPublicKey(); + SubjectPublicKeyInfo spi = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pub); + rpk.SetSubjectPublicKeyInfo(spi); + + AuthenticationKey = cwt.Cnf.Key; + return; + } + catch { + } + + TlsEvent ev = new TlsEvent(TlsEvent.EventCode.ServerCertificate) { + Certificate = rpk + }; + + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(this, ev); + } + + if (!ev.Processed) { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + + AuthenticationKey = ev.KeyValue; + + } +#endif + public virtual void NotifyServerCertificate(Certificate serverCertificate) { /* @@ -382,7 +529,21 @@ public virtual TlsCredentials GetClientCredentials(CertificateRequest certificat SignatureAlgorithm.rsa, "x509-client.pem", "x509-client-key.pem"); */ #endif - // If we did not fine appropriate signer credientials - ask for help +#if SUPPORT_TLS_CWT + + if (TlsKey.CertType == CertificateType.CwtPublicKey) { + OneKey k = TlsKey.PublicCwt.Cnf.Key; + if (k.HasKeyType((int)COSE.GeneralValuesInt.KeyType_EC2) && + k.HasAlgorithm(COSE.AlgorithmValues.ECDSA_256)) { + + AsymmetricKeyParameter privKey = TlsKey.PrivateKey.AsPrivateKey(); + + return new DefaultTlsSignerCredentials(_mContext, new CwtPublicKey(TlsKey.PublicCwt.EncodeToBytes()), privKey, new SignatureAndHashAlgorithm(HashAlgorithm.sha256, SignatureAlgorithm.ecdsa)); + } + } +#endif + + // If we did not fine appropriate signer credentials - ask for help TlsEvent e = new TlsEvent(TlsEvent.EventCode.SignCredentials) { CipherSuite = KeyExchangeAlgorithm.ECDHE_ECDSA diff --git a/CoAP.NET/DTLS/DTLSClientChannel.cs b/CoAP.NET/DTLS/DTLSClientChannel.cs index 6e3c238..72597f4 100644 --- a/CoAP.NET/DTLS/DTLSClientChannel.cs +++ b/CoAP.NET/DTLS/DTLSClientChannel.cs @@ -5,6 +5,9 @@ using System.Net.Sockets; using Com.AugustCellars.CoAP.Channel; using Com.AugustCellars.COSE; +#if SUPPORT_TLS_CWT +using Com.AugustCellars.WebToken; +#endif namespace Com.AugustCellars.CoAP.DTLS { @@ -23,6 +26,10 @@ internal class DTLSClientChannel : IChannel private readonly int _port; private UDPChannel _udpChannel; private readonly OneKey _userKey; +#if SUPPORT_TLS_CWT + private readonly CWT _userCwt; +#endif + private KeySet CwtTrustKeySet { get; } public EventHandler TlsEventHandler; @@ -46,6 +53,16 @@ public DTLSClientChannel(OneKey userKey, Int32 port) _userKey = userKey; } +#if SUPPORT_TLS_CWT + public DTLSClientChannel(CWT cwt, OneKey userKey, KeySet cwtTrustKeys, int port) + { + _port = port; + _userKey = userKey; + _userCwt = cwt; + CwtTrustKeySet = cwtTrustKeys; + } +#endif + /// /// Create a client only channel and use a given endpoint /// @@ -92,6 +109,11 @@ public Int32 ReceivePacketSize { private Int32 _running; + /// + public bool AddMulticastAddress(IPEndPoint ep) + { + return false; + } /// /// Tell the channel to set itself up and start processing data @@ -180,7 +202,17 @@ public ISession GetSession(System.Net.EndPoint ep) // No session - create a new one. - session = new DTLSSession(ipEndPoint, DataReceived, _userKey); +#if SUPPORT_TLS_CWT + if (_userCwt != null) { + session = new DTLSSession(ipEndPoint, DataReceived, _userCwt, _userKey, CwtTrustKeySet); + } + else { +#endif + session = new DTLSSession(ipEndPoint, DataReceived, _userKey); +#if SUPPORT_TLS_CWT + } +#endif + session.TlsEventHandler += OnTlsEvent; AddSession(session); diff --git a/CoAP.NET/DTLS/DTLSClientEndPoint.cs b/CoAP.NET/DTLS/DTLSClientEndPoint.cs index 52034b1..28f8ab6 100644 --- a/CoAP.NET/DTLS/DTLSClientEndPoint.cs +++ b/CoAP.NET/DTLS/DTLSClientEndPoint.cs @@ -4,6 +4,9 @@ using Com.AugustCellars.CoAP.Net; using Com.AugustCellars.COSE; +#if SUPPORT_TLS_CWT +using Com.AugustCellars.WebToken; +#endif namespace Com.AugustCellars.CoAP.DTLS { @@ -24,6 +27,12 @@ public DTLSClientEndPoint(OneKey userKey) : this(userKey, 0, CoapConfig.Default) { } +#if SUPPORT_TLS_CWT + public DTLSClientEndPoint(CWT cwt, OneKey privKey, KeySet cwtTrustKeys) : this (new DTLSClientChannel(cwt, privKey, cwtTrustKeys, 0), CoapConfig.Default) + { + } +#endif + /// /// Instantiates a new DTLS endpoint with the specific channel and configuration /// diff --git a/CoAP.NET/DTLS/DTLSEndPoint.cs b/CoAP.NET/DTLS/DTLSEndPoint.cs index cf634ac..db8a617 100644 --- a/CoAP.NET/DTLS/DTLSEndPoint.cs +++ b/CoAP.NET/DTLS/DTLSEndPoint.cs @@ -13,31 +13,31 @@ namespace Com.AugustCellars.CoAP.DTLS public class DTLSEndPoint : CoAPEndPoint { /// - public DTLSEndPoint(KeySet serverKeys, KeySet userKeys) : this(serverKeys, userKeys, 0, CoapConfig.Default) + public DTLSEndPoint(TlsKeyPairSet serverKeys, KeySet userKeys) : this(serverKeys, userKeys, 0, CoapConfig.Default) { } /// - public DTLSEndPoint(KeySet serverKeys, KeySet userKeys, ICoapConfig config) : this(serverKeys, userKeys, 0, config) + public DTLSEndPoint(TlsKeyPairSet serverKeys, KeySet userKeys, ICoapConfig config) : this(serverKeys, userKeys, 0, config) { } /// - public DTLSEndPoint(KeySet keysServer, KeySet keysUser, Int32 port) : this(new DTLSChannel(keysServer, keysUser, port), CoapConfig.Default) + public DTLSEndPoint(TlsKeyPairSet keysServer, KeySet keysUser, Int32 port, KeySet cwtTrustRoots = null) : this(new DTLSChannel(keysServer, keysUser, port, cwtTrustRoots), CoapConfig.Default) { } /// - public DTLSEndPoint(KeySet keyServer, KeySet keysUser, int port, ICoapConfig config) : this (new DTLSChannel(keyServer, keysUser, port), config) + public DTLSEndPoint(TlsKeyPairSet keyServer, KeySet keysUser, int port, ICoapConfig config) : this (new DTLSChannel(keyServer, keysUser, port), config) { } /// - public DTLSEndPoint(KeySet keysServer, KeySet keysUser, System.Net.EndPoint localEndPoint) : this(keysServer, keysUser, localEndPoint, CoapConfig.Default) + public DTLSEndPoint(TlsKeyPairSet keysServer, KeySet keysUser, System.Net.EndPoint localEndPoint) : this(keysServer, keysUser, localEndPoint, CoapConfig.Default) { } /// - public DTLSEndPoint(KeySet keysServer, KeySet keysUser, System.Net.EndPoint localEndPoint, ICoapConfig config) : this(new DTLSChannel(keysServer, keysUser, localEndPoint), config) + public DTLSEndPoint(TlsKeyPairSet keysServer, KeySet keysUser, System.Net.EndPoint localEndPoint, ICoapConfig config) : this(new DTLSChannel(keysServer, keysUser, localEndPoint), config) { } diff --git a/CoAP.NET/DTLS/DTLSSession.cs b/CoAP.NET/DTLS/DTLSSession.cs index c3c3ae9..ee0afa1 100644 --- a/CoAP.NET/DTLS/DTLSSession.cs +++ b/CoAP.NET/DTLS/DTLSSession.cs @@ -6,7 +6,9 @@ using System.Threading; using Com.AugustCellars.CoAP.Channel; using Com.AugustCellars.COSE; - +#if SUPPORT_TLS_CWT +using Com.AugustCellars.WebToken; +#endif using PeterO.Cbor; using Org.BouncyCastle.Crypto.Tls; @@ -25,8 +27,11 @@ public class DTLSSession : ISecureSession private DtlsTransport _dtlsSession; private readonly OurTransport _transport; private readonly OneKey _userKey; +#if SUPPORT_TLS_CWT + private readonly CWT _userCwt; +#endif private readonly KeySet _userKeys; - private readonly KeySet _serverKeys; + private readonly TlsKeyPairSet _serverKeys; private OneKey _authKey; private readonly ConcurrentQueue _queue = new ConcurrentQueue(); @@ -35,6 +40,7 @@ public class DTLSSession : ISecureSession private readonly EventHandler _sessionEvents; public EventHandler TlsEventHandler; + private KeySet CwtTrustKeySet { get; } /// /// List of event handlers to inform about session events. @@ -49,14 +55,27 @@ public DTLSSession(IPEndPoint ipEndPoint, EventHandler da _transport = new OurTransport(ipEndPoint); } - public DTLSSession(IPEndPoint ipEndPoint, EventHandler dataReceived, KeySet serverKeys, KeySet userKeys) + public DTLSSession(IPEndPoint ipEndPoint, EventHandler dataReceived, TlsKeyPairSet serverKeys, KeySet userKeys, KeySet cwtTrustKeySet) { _ipEndPoint = ipEndPoint; _dataReceived = dataReceived; _userKeys = userKeys; _serverKeys = serverKeys; _transport = new OurTransport(ipEndPoint); + CwtTrustKeySet = cwtTrustKeySet; + } + +#if SUPPORT_TLS_CWT + public DTLSSession(IPEndPoint ipEndPoint, EventHandler dataReceived, CWT userCwt, OneKey privKey, KeySet cwtTrustKeys) + { + _ipEndPoint = ipEndPoint; + _dataReceived = dataReceived; + _userCwt = userCwt; + _userKey = privKey; + CwtTrustKeySet = cwtTrustKeys; + _transport = new OurTransport(ipEndPoint); } +#endif public OneKey AuthenticationKey { @@ -104,7 +123,15 @@ public void Connect(UDPChannel udpChannel) { BasicTlsPskIdentity pskIdentity = null; - if (_userKey != null) { +#if SUPPORT_TLS_CWT + if (_userCwt != null) + { + _client = new DtlsClient(null, new TlsKeyPair(_userCwt, _userKey), CwtTrustKeySet); + } + else if (_userKey != null) { +#else + if (_userKey != null) { +#endif if (_userKey.HasKeyType((int) COSE.GeneralValuesInt.KeyType_Octet)) { CBORObject kid = _userKey[COSE.CoseKeyKeys.KeyIdentifier]; @@ -153,6 +180,7 @@ public void Accept(UDPChannel udpChannel, byte[] message) DtlsServer server = new DtlsServer(_serverKeys, _userKeys); server.TlsEventHandler += OnTlsEvent; + server.CwtTrustKeySet = CwtTrustKeySet; _transport.UDPChannel = udpChannel; _transport.Receive(message); @@ -269,16 +297,16 @@ void StartListen() else { byte[] buf2 = new byte[size]; Array.Copy(buf, buf2, size); - FireDataReceived(buf2, _ipEndPoint); + FireDataReceived(buf2, _ipEndPoint, null); // M00BUG } } } - private void FireDataReceived(Byte[] data, System.Net.EndPoint ep) + private void FireDataReceived(Byte[] data, System.Net.EndPoint ep, System.Net.EndPoint epLocal) { EventHandler h = _dataReceived; if (h != null) { - h(this, new DataReceivedEventArgs(data, ep, this)); + h(this, new DataReceivedEventArgs(data, ep, epLocal, this)); } } diff --git a/CoAP.NET/DTLS/DtlsServer.cs b/CoAP.NET/DTLS/DtlsServer.cs index 91df2da..3ba6e22 100644 --- a/CoAP.NET/DTLS/DtlsServer.cs +++ b/CoAP.NET/DTLS/DtlsServer.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,6 +9,9 @@ using Org.BouncyCastle.Crypto.Tls; using Com.AugustCellars.COSE; +#if SUPPORT_TLS_CWT +using Com.AugustCellars.WebToken; +#endif using Org.BouncyCastle.Asn1.Nist; using Org.BouncyCastle.Asn1.X509; using Org.BouncyCastle.Asn1.X9; @@ -23,12 +27,14 @@ namespace Com.AugustCellars.CoAP.DTLS { class DtlsServer : DefaultTlsServer { - private KeySet _serverKeys; + private TlsKeyPairSet _serverKeys; private KeySet _userKeys; public EventHandler TlsEventHandler; - internal DtlsServer(KeySet serverKeys, KeySet userKeys) + public KeySet CwtTrustKeySet { get; set; } + + internal DtlsServer(TlsKeyPairSet serverKeys, KeySet userKeys) { _serverKeys = serverKeys; _userKeys = userKeys; @@ -118,28 +124,110 @@ private BigInteger ConvertBigNum(CBORObject cbor) return new BigInteger(rgb2); } +#if SUPPORT_TLS_CWT + public override AbstractCertificate ParseCertificate(short certificateType, Stream io) + { + switch (certificateType) + { + case CertificateType.CwtPublicKey: + try + { + CwtPublicKey cwtPub = CwtPublicKey.Parse(io); + + CWT cwtServer = CWT.Decode(cwtPub.EncodedCwt(), CwtTrustKeySet, CwtTrustKeySet); + + AsymmetricKeyParameter pubKey = cwtServer.Cnf.Key.AsPublicKey(); + + SubjectPublicKeyInfo spi = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pubKey); + cwtPub.SetSubjectPublicKeyInfo(spi); + + return cwtPub; + } + catch + { + return null; + } + + default: + return null; + } + } +#endif protected override TlsSignerCredentials GetECDsaSignerCredentials() { + byte[] certTypes; + + if (mClientExtensions.Contains(ExtensionType.server_certificate_type)) { + certTypes = (byte[]) mClientExtensions[ExtensionType.server_certificate_type]; + } + else { + certTypes = new byte[]{1}; + } + + foreach (byte b in certTypes) { #if SUPPORT_RPK - foreach (OneKey k in _serverKeys) { - if (k.HasKeyType((int) COSE.GeneralValuesInt.KeyType_EC2) && - k.HasAlgorithm(COSE.AlgorithmValues.ECDSA_256)) { + if (b == 2) { + foreach (TlsKeyPair kp in _serverKeys) { + if (b != kp.CertType) continue; - X9ECParameters p = k.GetCurve(); - ECDomainParameters parameters = new ECDomainParameters(p.Curve, p.G, p.N, p.H); - ECPrivateKeyParameters privKey = new ECPrivateKeyParameters("ECDSA", ConvertBigNum(k[CoseKeyParameterKeys.EC_D]), parameters); + OneKey k = kp.PublicKey; + if (k.HasKeyType((int) COSE.GeneralValuesInt.KeyType_EC2) && + k.HasAlgorithm(COSE.AlgorithmValues.ECDSA_256)) { - ECPoint point = k.GetPoint(); - ECPublicKeyParameters param = new ECPublicKeyParameters(point, parameters); + AsymmetricKeyParameter param = k.AsPublicKey(); - SubjectPublicKeyInfo spi = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(param); + SubjectPublicKeyInfo spi = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(param); - return new DefaultTlsSignerCredentials(mContext, new RawPublicKey(spi), privKey, new SignatureAndHashAlgorithm(HashAlgorithm.sha256, SignatureAlgorithm.ecdsa) ); + return new DefaultTlsSignerCredentials(mContext, new RawPublicKey(spi), kp.PrivateKey.AsPrivateKey(), + new SignatureAndHashAlgorithm( + HashAlgorithm.sha256, SignatureAlgorithm.ecdsa)); + } + } + } +#endif +#if SUPPORT_TLS_CWT + if (b == 254) { + foreach (TlsKeyPair kp in _serverKeys) { + if (b != kp.CertType) continue; + + OneKey k = kp.PrivateKey; + if (k.HasKeyType((int) COSE.GeneralValuesInt.KeyType_EC2) && + k.HasAlgorithm(COSE.AlgorithmValues.ECDSA_256)) { + + CwtPublicKey cwtKey = new CwtPublicKey(kp.PublicCwt.EncodeToBytes()); + AsymmetricKeyParameter pubKey = kp.PublicCwt.Cnf.Key.AsPublicKey(); + cwtKey.SetSubjectPublicKeyInfo(SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pubKey)); + + return new DefaultTlsSignerCredentials( + mContext, cwtKey, kp.PrivateKey.AsPrivateKey(), + new SignatureAndHashAlgorithm(HashAlgorithm.sha256, SignatureAlgorithm.ecdsa)); + } + } } - } #endif +#if SUPPORT_RPK + if (b == 1) { + foreach (TlsKeyPair kp in _serverKeys) + { + if (b != kp.CertType) continue; + + OneKey k = kp.PrivateKey; + if (k.HasKeyType((int)COSE.GeneralValuesInt.KeyType_EC2) && + k.HasAlgorithm(COSE.AlgorithmValues.ECDSA_256)) { + + return new DefaultTlsSignerCredentials( + mContext, + new CwtPublicKey(kp.PublicCwt.EncodeToBytes()), + kp.PrivateKey.AsPrivateKey(), + new SignatureAndHashAlgorithm(HashAlgorithm.sha256, SignatureAlgorithm.ecdsa)); + } + } + } +#endif + + } - // If we did not fine appropriate signer credientials - ask for help + // If we did not fine appropriate signer credentials - ask for help TlsEvent e = new TlsEvent(TlsEvent.EventCode.SignCredentials) { CipherSuite = KeyExchangeAlgorithm.ECDHE_ECDSA @@ -192,6 +280,10 @@ protected virtual TlsKeyExchange CreatePskKeyExchange(int keyExchange) protected override TlsKeyExchange CreateECDHKeyExchange(int keyExchange) { + byte[] serverCertTypes; + if (mClientExtensions.Contains(ExtensionType.server_certificate_type)) { + serverCertTypes = (byte[]) mClientExtensions[ExtensionType.server_certificate_type]; + } return new TlsECDHKeyExchange(keyExchange, mSupportedSignatureAlgorithms, mNamedCurves, mClientECPointFormats, mServerECPointFormats); } @@ -215,6 +307,7 @@ public override byte GetClientCertificateType(byte[] certificateTypes) foreach (byte type in certificateTypes) { if (type == 2) return type; // Assume we only support Raw Public Key + if (type == 254) return type; } @@ -239,6 +332,7 @@ public override byte GetServerCertificateType(byte[] certificateTypes) foreach (byte type in certificateTypes) { if (type == 2) return type; // Assume we only support Raw Public Key + if (type == 254) return type; } throw new TlsFatalAlert(AlertDescription.handshake_failure); } @@ -263,6 +357,12 @@ public override void NotifyClientCertificate(AbstractCertificate clientCertifica if (clientCertificate is RawPublicKey) { mPskIdentityManager.GetRpkKey((RawPublicKey) clientCertificate); } +#if SUPPORT_TLS_CWT + else if (clientCertificate is CwtPublicKey) { + mPskIdentityManager.CwtTrustRoots = CwtTrustKeySet; + mPskIdentityManager.GetCwtKey((CwtPublicKey) clientCertificate); + } +#endif else { TlsEvent e = new TlsEvent(TlsEvent.EventCode.ClientCertificate) { Certificate = clientCertificate @@ -312,6 +412,11 @@ internal MyIdentityManager(KeySet keys) public OneKey AuthenticationKey { get; private set; } +#if SUPPORT_TLS_CWT + public KeySet CwtTrustRoots { get; set; } + public CWT CwtAuthenticationKey { get; private set; } +#endif + public virtual byte[] GetHint() { return Encoding.UTF8.GetBytes("hint"); @@ -405,6 +510,41 @@ public void GetRpkKey(RawPublicKey rpk) } } #endif + +#if SUPPORT_TLS_CWT + public void GetCwtKey(CwtPublicKey rpk) + { + AsymmetricKeyParameter key; + CWT cwt; + + try { + cwt = CWT.Decode(rpk.EncodedCwt(), CwtTrustRoots, CwtTrustRoots); + + AuthenticationKey = cwt.Cnf.Key; + } + catch (Exception e) + { + TlsEvent ev = new TlsEvent(TlsEvent.EventCode.ClientCertificate) + { + Certificate = rpk + }; + + EventHandler handler = TlsEventHandler; + if (handler != null) + { + handler(this, ev); + } + + if (!ev.Processed) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + + AuthenticationKey = ev.KeyValue; + } + } +#endif + } } } diff --git a/CoAP.NET/DTLS/TlsKey.cs b/CoAP.NET/DTLS/TlsKey.cs new file mode 100644 index 0000000..f3afda3 --- /dev/null +++ b/CoAP.NET/DTLS/TlsKey.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Com.AugustCellars.COSE; +#if SUPPORT_TLS_CWT +using Com.AugustCellars.WebToken; +#endif +using Org.BouncyCastle.Security; + +namespace Com.AugustCellars.CoAP.DTLS +{ + public class TlsKeyPair + { + public OneKey PrivateKey { get; } + public OneKey PublicKey { get; } +#if SUPPORT_TLS_CWT + public CWT PublicCwt { get; } +#endif + public byte[] X509Certificate { get; } + + public byte CertType { get; } + + public TlsKeyPair(OneKey publicKey, OneKey privateKey) + { + this.PrivateKey = privateKey; + this.PublicKey = publicKey; + CertType = 2; // RPK + } + +#if SUPPORT_TLS_CWT + public TlsKeyPair(CWT publicKey, OneKey privateKey) + { + this.PrivateKey = privateKey; + this.PublicCwt = publicKey; + CertType = 254; // CWT + } +#endif + + public TlsKeyPair(byte[] certificate, OneKey privateKey) + { + this.PrivateKey = privateKey; + this.X509Certificate = certificate; + CertType = 1; // X.509 Certificate + } + + public bool Compare(TlsKeyPair other) + { + return false; + } + } + + public class TlsKeyPairSet + { + List _keyList = new List(); + + /// + /// Return number of keys in the key set. + /// + public int Count => _keyList.Count; + + /// + /// Return first key in the set. + /// + public TlsKeyPair FirstKey => _keyList.First(); + + /// + /// Return the i-th element in the key set. + /// + /// index of element to return + /// OneKey + public TlsKeyPair this[int i] => _keyList[i]; + + /// + /// Add a key to the key set. The function will do a minimal check for equality to existing keys in the set. + /// + /// OneKey: key to be added + public void AddKey(TlsKeyPair key) + { + foreach (TlsKeyPair k in _keyList) + { + if (key.Compare(k)) + return; + } + _keyList.Add(key); + } + + /// + /// All forall to be used to enumerate the keys in a key set. + /// + /// + public IEnumerator GetEnumerator() + { + return _keyList.GetEnumerator(); + } + + + } +} diff --git a/CoAP.NET/EndPoint/Resources/RemoteResource.cs b/CoAP.NET/EndPoint/Resources/RemoteResource.cs index c794e4f..5200c75 100644 --- a/CoAP.NET/EndPoint/Resources/RemoteResource.cs +++ b/CoAP.NET/EndPoint/Resources/RemoteResource.cs @@ -23,8 +23,10 @@ public static RemoteResource NewRoot(String linkFormat, int mediaType = MediaTyp case MediaType.ApplicationLinkFormat: return LinkFormat.Deserialize(linkFormat); +#if false // Dead work? case MediaType.ApplicationLinkFormatJson: return LinkFormat.DeserializeJson(linkFormat); +#endif default: throw new ArgumentException("Unrecognized media type"); @@ -37,11 +39,13 @@ public static RemoteResource NewRoot(byte[] linkFormat, int mediaType = MediaTyp case MediaType.ApplicationLinkFormat: return LinkFormat.Deserialize(Encoding.UTF8.GetString(linkFormat)); +#if false // Dead work? case MediaType.ApplicationLinkFormatCbor: return LinkFormat.DeserializeCbor(linkFormat); case MediaType.ApplicationLinkFormatJson: return LinkFormat.DeserializeJson(Encoding.UTF8.GetString(linkFormat)); +#endif default: throw new ArgumentException("Unrecognized media type"); diff --git a/CoAP.NET/LinkFormat.cs b/CoAP.NET/LinkFormat.cs index b83e476..3460001 100644 --- a/CoAP.NET/LinkFormat.cs +++ b/CoAP.NET/LinkFormat.cs @@ -13,10 +13,11 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using System.Text.RegularExpressions; +using Com.AugustCellars.CoAP.Coral; using Com.AugustCellars.CoAP.EndPoint.Resources; using Com.AugustCellars.CoAP.Log; using Com.AugustCellars.CoAP.Server.Resources; +using Com.AugustCellars.CoAP.Util; using PeterO.Cbor; namespace Com.AugustCellars.CoAP @@ -38,7 +39,7 @@ public static class LinkFormat /// /// What is the set of attributes that must appear only once in a link format /// - public static string[] SingleOccuranceAttributes = new string[] { + public static string[] SingleOccurenceAttributes = new string[] { "title", "sz", "obs" }; @@ -94,7 +95,7 @@ public static class LinkFormat /// public static readonly string Separator = ";"; - #if false +#if false public static readonly Regex DelimiterRegex = new Regex("\\s*" + Delimiter + "+\\s*"); public static readonly Regex SeparatorRegex = new Regex("\\s*" + Separator + "+\\s*"); @@ -123,6 +124,51 @@ public static class LinkFormat ["obs"] = CBORObject.FromObject(13) }; + public static readonly Dictionary CborCoralKeys = new Dictionary() { + ["hreflang"] = CBORObject.FromObject(10), + ["media"] = CBORObject.FromObject(11), + ["title"] = CBORObject.FromObject(12), + ["type"] = CBORObject.FromObject(13), + ["rt"] = CBORObject.FromObject(14), + ["if"] = CBORObject.FromObject(15), + ["sz"] = CBORObject.FromObject(16), + ["ct"] = CBORObject.FromObject(17), + ["ct"] = CBORObject.FromObject(18), + ["obs"] = CBORObject.FromObject(20) + }; + + public static readonly Dictionary CoralsKeys = new Dictionary() { + ["ct"] = "http://coreapps.org/reef#ct", + ["sz"] = "http://coreapps.org/reef#sz", + ["if"] = "http://coreapps.org/reef#if", + ["rt"] = "http://coreapps.org/reef#rt", + ["type"] = "http://coreapps.org/coap#type", + ["media"] = "http://coreapps.org/coap#media", + }; + + public static CoralDictionary ReefDictionary = new CoralDictionary() { + {0, "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"}, + {1, "http://www.iana.org/assignments/relation/item"}, + {2, "http://www.iana.org/assignments/relation/collection"}, + {3, "http://coreapps.org/collections#create"}, + {4, "http://coreapps.org/base#update"}, + {5, "https://coreapps.org/collecitons#delete"}, + {6, "http://coreapps.org/base#search"}, + {7, "http://coreapps.org/coap#accept"}, + {8, "http://coreapps.org/reef#rd-unit"}, + {9, "http://coreapps.org/reef#rd-item"}, + {10, "http://coreapps.org/base#lang"}, + {11, "http://coreapps.org/reef#media"}, + {12, "http://coreapps.org/reef#title"}, + {13, "http://coreapps.org/reef#type"}, + {14, "http://coreapps.org/reef#rt"}, + {15, "http://coreapps.org/reef#if"}, + {16, "http://coreapps.org/reef#sz"}, + {17, "http://coreapps.org/reef#ct"}, + {18, "/.well-known/core"} + + }; + /// /// Serialize resources starting at a resource node into WebLink format /// @@ -157,6 +203,7 @@ public static string Serialize(IResource root, IEnumerable queries) return linkFormat.ToString(); } +#if false // Work is dead? public static byte[] SerializeCbor(IResource root, IEnumerable queries) { CBORObject linkFormat = CBORObject.NewArray(); @@ -184,6 +231,21 @@ public static string SerializeJson(IResource root, IEnumerable queries) return linkFormat.ToJSONString(); } +#endif + public static byte[] SerializeCoral(IResource root, IEnumerable queries) + { + CoralBody nodeRoot = new CoralBody(); + + + List queryList = null; + if (queries != null) queryList = queries.ToList(); + + foreach (IResource child in root.Children) { + SerializeTreeInCoral(child, queryList, nodeRoot, CborAttributeKeys); + } + + return nodeRoot.EncodeToBytes(ReefDictionary); + } public static IEnumerable Parse(string linkFormat) { @@ -204,7 +266,7 @@ public static IEnumerable Parse(string linkFormat) int eq = attributes[i].IndexOf('='); string name = eq == -1 ? attributes[i] : attributes[i].Substring(0, eq); - if (ParseStrictMode && SingleOccuranceAttributes.Contains(name)) { + if (ParseStrictMode && SingleOccurenceAttributes.Contains(name)) { throw new ArgumentException($"'{name}' occurs multiple times"); } @@ -226,6 +288,7 @@ public static IEnumerable Parse(string linkFormat) } } +#if false // Work is dead? public static IEnumerable ParseCbor(byte[] linkFormat) { CBORObject links = CBORObject.DecodeFromBytes(linkFormat); @@ -237,7 +300,7 @@ public static IEnumerable ParseJson(string linkFormat) CBORObject links = CBORObject.FromJSONString(linkFormat); return ParseCommon(links, null); } - +#endif private static IEnumerable ParseCommon(CBORObject links, Dictionary dictionary) { if (links.Type != CBORType.Array) throw new ArgumentException("Not an array"); @@ -265,7 +328,7 @@ private static IEnumerable ParseCommon(CBORObject links, Dictionary queries, Stri if (resource.Children == null) return; // sort by resource name - List childrens = new List(resource.Children); - childrens.Sort((r1, r2) => string.CompareOrdinal(r1.Name, r2.Name)); + List children = new List(resource.Children); + children.Sort((r1, r2) => string.CompareOrdinal(r1.Name, r2.Name)); - foreach (IResource child in childrens) { + foreach (IResource child in children) { SerializeTree(child, queries, sb); } } +#if false // Work is dead? private static void SerializeTree(IResource resource, List queries, CBORObject cbor, Dictionary dictionary) { if (resource.Visible && Matches(resource, queries)) { @@ -322,20 +386,40 @@ private static void SerializeTree(IResource resource, List queries, CBOR if (resource.Children == null) return; // sort by resource name - List childrens = new List(resource.Children); - childrens.Sort((r1, r2) => string.CompareOrdinal(r1.Name, r2.Name)); + List children = new List(resource.Children); + children.Sort((r1, r2) => string.CompareOrdinal(r1.Name, r2.Name)); - foreach (IResource child in childrens) { + foreach (IResource child in children) { SerializeTree(child, queries, cbor, dictionary); } } +#endif + + private static void SerializeTreeInCoral(IResource resource, List queries, CoralBody coral, + Dictionary dictionary) + { + + if (resource.Visible && Matches(resource, queries)) { + SerializeResourceInCoral(resource, coral, dictionary); + } + + if (resource.Children == null) return; + + // sort by resource name + List children = new List(resource.Children); + children.Sort((r1, r2) => string.CompareOrdinal(r1.Name, r2.Name)); + + foreach (IResource child in children) { + SerializeTreeInCoral(child, queries, coral, dictionary); + } + } public static void SerializeResource(IResource resource, StringBuilder sb, ResourceAttributes otherAttributes = null, Uri uriRelative = null) { sb.Append("<"); if (uriRelative != null) { - sb.Append(new Uri(uriRelative, resource.Path + resource.Name)); + sb.Append(new Uri(uriRelative, resource.Path + resource.Name).ToString()); } else { sb.Append(resource.Path) @@ -348,6 +432,7 @@ public static void SerializeResource(IResource resource, StringBuilder sb, Resou } } +#if false // Work is dead? public static void SerializeResource(IResource resource, CBORObject cbor, Dictionary dictionary, ResourceAttributes otherAttributes = null, Uri uriRelative = null) { @@ -373,6 +458,36 @@ public static void SerializeResource(IResource resource, CBORObject cbor, Dictio cbor.Add(obj); } +#endif + + public static void SerializeResourceInCoral(IResource resource, CoralBody coral, + Dictionary dictionary, + ResourceAttributes otherAttributes = null, Uri uriRelative = null, + bool isEndPoint = false) + { + CBORObject obj = CBORObject.NewArray(); + CBORObject href; + if (uriRelative == null) { + href =Ciri.ToCbor(resource.Path + resource.Name); + } + else { + href = Ciri.ToCbor(new Uri(uriRelative, resource.Path + resource.Name)); + } + + CoralBody body = new CoralBody(); + + SerializeAttributesInCoral(resource.Attributes, body, dictionary, uriRelative); + if (otherAttributes != null) { + SerializeAttributesInCoral(otherAttributes, body, dictionary, uriRelative); + } + + if (body.Length == 0) { + body = null; + } + + CoralItem item = new CoralLink(isEndPoint ? "http://coreapps.org/ref#rd-unit" : "http://coreapps.org/reef#rd-item", href, body); + coral.Add(item); + } private static void SerializeAttributes(ResourceAttributes attributes, StringBuilder sb, Uri uriRelative) { @@ -397,6 +512,7 @@ private static void SerializeAttributes(ResourceAttributes attributes, StringBui } } +#if false // Work is dead? private static void SerializeAttributes(ResourceAttributes attributes, CBORObject cbor, Dictionary dictionary, Uri uriRelative) { List keys = new List(attributes.Keys); @@ -419,6 +535,37 @@ private static void SerializeAttributes(ResourceAttributes attributes, CBORObjec SerializeAttribute(name, values, cbor, dictionary); } } +#endif + + private static void SerializeAttributesInCoral(ResourceAttributes attributes, CoralBody coral, Dictionary dictionary, Uri uriRelative) + { + List keys = new List(attributes.Keys); + keys.Sort(); + foreach (string name in keys) { + if (!CoralsKeys.ContainsKey(name)) { + continue; + } + + List values = new List(attributes.GetValues(name)); + if (values.Count == 0) + { + continue; + } + + if (uriRelative != null && name == "anchor") + { + List newValues = new List(); + foreach (string val in values) + { + newValues.Add(new Uri(uriRelative, val).ToString()); + } + + values = newValues; + } + + SerializeAttributeInCoral(name, values, coral, null); + } + } private static void SerializeAttribute(string name, List values, StringBuilder sb) { @@ -469,16 +616,12 @@ private static void SerializeAttribute(string name, List values, StringB } } +#if false // Work is dead? private static void SerializeAttribute(string name, List values, CBORObject cbor, Dictionary dictionary) { bool useSpace = SpaceSeparatedValueAttributes.Contains(name); CBORObject result; - CBORObject nameX; - if (dictionary == null || !dictionary.TryGetValue(name, out nameX)) { - nameX = CBORObject.FromObject(name); - } - if (useSpace && values.Count > 1) { StringBuilder sb = new StringBuilder(); @@ -503,7 +646,50 @@ private static void SerializeAttribute(string name, List values, CBORObj } } - cbor.Add(nameX, result); + CBORObject pair = CBORObject.NewArray(); + pair.Add(name); + pair.Add(result); + cbor.Add(pair); + } +#endif + private static void SerializeAttributeInCoral(string name, List values, CoralBody coral, + Dictionary dictionary) + { + bool useSpace = SpaceSeparatedValueAttributes.Contains(name); + CBORObject result; + + string nameX = CoralsKeys[name]; + + if (useSpace && values.Count > 1) { + StringBuilder sb = new StringBuilder(); + + foreach (string value in values) { + sb.Append(value); + sb.Append(" "); + } + + sb.Length = sb.Length - 1; + + result = CBORObject.FromObject(sb.ToString()); + } + else if (values.Count == 1) { + string value = values.First(); + result = string.IsNullOrEmpty(value) ? CBORObject.True : CBORObject.FromObject(values.First()); + } + else { + result = CBORObject.NewArray(); + foreach (string value in values) { + if (string.IsNullOrEmpty(value)) { + result.Add(CBORObject.True); + } + else { + result.Add(value); + } + } + } + + CoralLink link = new CoralLink(nameX, result); + coral.Add(link); } private static bool IsNumber(string value) @@ -585,6 +771,9 @@ public static RemoteResource Deserialize(string linkFormat) return root; } + +#if false // Work is dead? + /// /// Parse a CBOR encoded link format structure /// @@ -672,7 +861,7 @@ private static RemoteResource DeserializeCbor(CBORObject cbor) return root; } - +#endif #if false private static LinkAttribute ParseAttribute(Scanner scanner) @@ -825,7 +1014,7 @@ internal static bool AddAttribute(ICollection attributes, LinkAtt private static bool IsSingle(string name) { - return SingleOccuranceAttributes.Contains(name); + return SingleOccurenceAttributes.Contains(name); } private static string quoteChars = "'\""; diff --git a/CoAP.NET/Log/LogManager.cs b/CoAP.NET/Log/LogManager.cs index e0f9854..56fdf8f 100644 --- a/CoAP.NET/Log/LogManager.cs +++ b/CoAP.NET/Log/LogManager.cs @@ -134,7 +134,7 @@ public enum LogLevel /// Error, /// - /// Fatals only. + /// Fatal only. /// Fatal, /// diff --git a/CoAP.NET/MediaType.cs b/CoAP.NET/MediaType.cs index dc2e7d9..a94b9a9 100644 --- a/CoAP.NET/MediaType.cs +++ b/CoAP.NET/MediaType.cs @@ -24,140 +24,221 @@ public class MediaType /// /// undefined /// - public const Int32 Undefined = -1; + public const int Undefined = -1; /// /// text/plain; charset=utf-8 /// - public const Int32 TextPlain = 0; + public const int TextPlain = 0; /// /// text/xml /// - public const Int32 TextXml = 1; + [Obsolete("Media type was never registered")] + public const int TextXml = 1; /// /// text/csv /// - public const Int32 TextCsv = 2; + [Obsolete("Media type was never registered")] + public const int TextCsv = 2; /// /// text/html /// - public const Int32 TextHtml = 3; + [Obsolete("Media type was never registered")] + public const int TextHtml = 3; + /// + /// Application/cose; cose-type="cose-encrypt0" + /// + public const int ApplicationCoseEncrypt0 = 16; + /// + /// Application/cose; cose-type="cose-mac0" + /// + public const int ApplicationCoseMac0 = 17; + /// + /// Application/cose; cose-type="cose-sign1" + /// + public const int ApplicationCoseSign1 = 18; /// /// image/gif /// - public const Int32 ImageGif = 21; + [Obsolete("Media type was never registered")] + public const int ImageGif = 21; /// /// image/jpeg /// - public const Int32 ImageJpeg = 22; + [Obsolete("Media type was never registered")] + public const int ImageJpeg = 22; /// /// image/png /// - public const Int32 ImagePng = 23; + [Obsolete("Media type was never registered")] + public const int ImagePng = 23; /// /// image/tiff /// - public const Int32 ImageTiff = 24; + [Obsolete("Media type was never registered")] + public const int ImageTiff = 24; /// /// audio/raw /// - public const Int32 AudioRaw = 25; + [Obsolete("Media type was never registered")] + public const int AudioRaw = 25; /// /// video/raw /// - public const Int32 VideoRaw = 26; + [Obsolete("Media type was never registered")] + public const int VideoRaw = 26; /// /// application/link-format /// - public const Int32 ApplicationLinkFormat = 40; + public const int ApplicationLinkFormat = 40; /// /// application/xml /// - public const Int32 ApplicationXml = 41; + public const int ApplicationXml = 41; /// /// application/octet-stream /// - public const Int32 ApplicationOctetStream = 42; + public const int ApplicationOctetStream = 42; /// /// application/rdf+xml /// - public const Int32 ApplicationRdfXml = 43; + [Obsolete("Media type was never registered")] + public const int ApplicationRdfXml = 43; /// /// application/soap+xml /// - public const Int32 ApplicationSoapXml = 44; + [Obsolete("Media type was never registered")] + public const int ApplicationSoapXml = 44; /// /// application/atom+xml /// - public const Int32 ApplicationAtomXml = 45; + [Obsolete("Media type was never registered")] + public const int ApplicationAtomXml = 45; /// /// application/xmpp+xml /// - public const Int32 ApplicationXmppXml = 46; + [Obsolete("Media type was never registered")] + public const int ApplicationXmppXml = 46; /// /// application/exi /// - public const Int32 ApplicationExi = 47; + public const int ApplicationExi = 47; /// /// application/fastinfoset /// - public const Int32 ApplicationFastinfoset = 48; + [Obsolete("Media type was never registered")] + public const int ApplicationFastinfoset = 48; /// /// application/soap+fastinfoset /// - public const Int32 ApplicationSoapFastinfoset = 49; + [Obsolete("Media type was never registered")] + public const int ApplicationSoapFastinfoset = 49; + /// + /// application/json [RFC 7159] + /// + public const int ApplicationJson = 50; /// - /// application/json + /// application/json-patch+json [RFC 6902] /// - public const Int32 ApplicationJson = 50; + public const int ApplicationJsonPatchJson = 51; /// /// application/x-obix-binary /// - public const Int32 ApplicationXObixBinary = 51; + [Obsolete("Media type was never registered")] + public const int ApplicationXObixBinary = 51; + /// + /// application/merge-patch+json [RFC 7396] + /// + public const int ApplicationMergePatchJson = 52; /// /// application/cbor - [RFC 7049] /// public const int ApplicationCbor = 60; + /// + /// application/cwt [RFC 8392] + /// + public const int ApplicationCwt = 61; + /// + /// application/multipart-core [draft-ietf-core-multipart-ct] + /// + public const int ApplicationMultipartCore = 62; + +#if false // Work is dead? /// /// application/link-format+cbor - [RFC TBD] /// + [Obsolete("Media type was never registered")] public const int ApplicationLinkFormatCbor = 64; +#endif + + public const int ApplicationCoseEncrypt = 96; + public const int ApplicationCoseMac = 97; + public const int ApplicationCoseSign = 98; + public const int ApplicationCoseKey = 101; + public const int ApplicationCoseKeySet = 102; + +#if false /// /// application/link-format+json - [RFC TBD] /// + [Obsolete("Media type was never registered")] public const int ApplicationLinkFormatJson = 504; +#endif + + /// + /// application/ace+cbor - [draft-ietf-ace-authz] + /// + public const int ApplicationAceCbor = 65000; + + public const int ApplicationCoralReef = 65088; + public const int Coral = 999; /// /// any /// - public const Int32 Any = 0xFF; + public const int Any = -2; + + public class MediaTypeInfo + { + public string[] ContentType { get; } + public bool IsText { get; } + public bool IsCbor { get; } + public MediaTypeInfo(string[] contentType, bool isText=false, bool isCbor=false) + { + ContentType = contentType; + IsText = isText; + IsCbor = isCbor; + } + } - private static readonly Dictionary registry = new Dictionary(); + private static readonly Dictionary registry = new Dictionary(); static MediaType() { - registry.Add(TextPlain, new String[] { "text/plain", "txt" }); - registry.Add(TextXml, new String[] { "text/xml", "xml" }); - registry.Add(TextCsv, new String[] { "text/csv", "csv" }); - registry.Add(TextHtml, new String[] { "text/html", "html" }); + registry.Add(TextPlain, new MediaTypeInfo(new string[] { "text/plain", "txt" }, true)); + registry.Add(TextXml, new MediaTypeInfo(new string[] { "text/xml", "xml" }, true)); + registry.Add(TextCsv, new MediaTypeInfo(new string[] { "text/csv", "csv" }, true)); + registry.Add(TextHtml, new MediaTypeInfo(new string[] { "text/html", "html" }, true)); + + registry.Add(ImageGif, new MediaTypeInfo(new string[] { "image/gif", "gif" })); + registry.Add(ImageJpeg, new MediaTypeInfo(new string[] { "image/jpeg", "jpg" })); + registry.Add(ImagePng, new MediaTypeInfo(new string[] { "image/png", "png" })); + registry.Add(ImageTiff, new MediaTypeInfo(new string[] { "image/tiff", "tif" })); + registry.Add(AudioRaw, new MediaTypeInfo(new string[] { "audio/raw", "raw" })); + registry.Add(VideoRaw, new MediaTypeInfo(new string[] { "video/raw", "raw" })); - registry.Add(ImageGif, new String[] { "image/gif", "gif" }); - registry.Add(ImageJpeg, new String[] { "image/jpeg", "jpg" }); - registry.Add(ImagePng, new String[] { "image/png", "png" }); - registry.Add(ImageTiff, new String[] { "image/tiff", "tif" }); - registry.Add(AudioRaw, new String[] { "audio/raw", "raw" }); - registry.Add(VideoRaw, new String[] { "video/raw", "raw" }); + registry.Add(ApplicationLinkFormat, new MediaTypeInfo(new string[] { "application/link-format", "wlnk" }, true)); + registry.Add(ApplicationXml, new MediaTypeInfo(new string[] { "application/xml", "xml" }, true)); + registry.Add(ApplicationOctetStream, new MediaTypeInfo(new string[] { "application/octet-stream", "bin" })); + registry.Add(ApplicationRdfXml, new MediaTypeInfo(new string[] {"application/rdf+xml", "rdf"}, true)); + registry.Add(ApplicationSoapXml, new MediaTypeInfo(new string[] {"application/soap+xml", "soap"}, true)); + registry.Add(ApplicationAtomXml, new MediaTypeInfo(new string[] {"application/atom+xml", "atom"}, true)); + registry.Add(ApplicationXmppXml, new MediaTypeInfo(new string[] {"application/xmpp+xml", "xmpp"}, true)); + registry.Add(ApplicationFastinfoset, new MediaTypeInfo(new string[] { "application/fastinfoset", "finf"})); + registry.Add(ApplicationSoapFastinfoset, new MediaTypeInfo(new string[] {"application/soap+fastinfoset", "soap.finf"})); + registry.Add(ApplicationXObixBinary, new MediaTypeInfo(new string[] { "application/x-obix-binary", "obix" })); + registry.Add(ApplicationExi, new MediaTypeInfo(new string[] { "application/exi", "exi" })); + registry.Add(ApplicationJson, new MediaTypeInfo(new string[] { "application/json", "json" }, false)); // Compressed w/ deflate - registry.Add(ApplicationLinkFormat, new String[] { "application/link-format", "wlnk" }); - registry.Add(ApplicationXml, new String[] { "application/xml", "xml" }); - registry.Add(ApplicationOctetStream, new String[] { "application/octet-stream", "bin" }); - registry.Add(ApplicationRdfXml, new String[] {"application/rdf+xml", "rdf"}); - registry.Add(ApplicationSoapXml, new String[] {"application/soap+xml", "soap"}); - registry.Add(ApplicationAtomXml, new String[] {"application/atom+xml", "atom"}); - registry.Add(ApplicationXmppXml, new String[] {"application/xmpp+xml", "xmpp"}); - registry.Add(ApplicationFastinfoset,new String[] { "application/fastinfoset", "finf"}); - registry.Add(ApplicationSoapFastinfoset, new String[] {"application/soap+fastinfoset", "soap.finf"}); - registry.Add(ApplicationXObixBinary, new String[] { "application/x-obix-binary", "obix" }); - registry.Add(ApplicationExi, new String[] { "application/exi", "exi" }); - registry.Add(ApplicationJson, new String[] { "application/json", "json" }); + registry.Add(Coral, new MediaTypeInfo(new string[] {"XX", "coral"}, false, true)); } /// @@ -165,31 +246,29 @@ static MediaType() /// /// The media type to be checked /// True iff the media type is a type of image - public static Boolean IsImage(Int32 mediaType) + public static Boolean IsImage(int mediaType) { return mediaType >= ImageGif && mediaType <= ImageTiff; } - public static Boolean IsPrintable(Int32 mediaType) + public static Boolean IsPrintable(int mediaType) { - switch (mediaType) - { - case TextPlain: - case TextXml: - case TextCsv: - case TextHtml: - case ApplicationLinkFormat: - case ApplicationXml: - case ApplicationRdfXml: - case ApplicationSoapXml: - case ApplicationAtomXml: - case ApplicationXmppXml: - case ApplicationJson: - case Undefined: - return true; - default: - return false; + MediaTypeInfo val; + if (!registry.TryGetValue(mediaType, out val)) { + return false; } + + return val.IsText; + } + + public static Boolean IsCbor(int mediaType) + { + MediaTypeInfo val; + if (!registry.TryGetValue(mediaType, out val)) { + return false; + } + + return val.IsCbor; } /// @@ -197,14 +276,12 @@ public static Boolean IsPrintable(Int32 mediaType) /// /// The media type to be described /// A string describing the media type - public static String ToString(Int32 mediaType) + public static string ToString(int mediaType) { - if (registry.ContainsKey(mediaType)) - { - return registry[mediaType][0]; + if (registry.ContainsKey(mediaType)) { + return registry[mediaType].ContentType[0]; } - else - { + else { return "unknown/" + mediaType; } } @@ -212,19 +289,18 @@ public static String ToString(Int32 mediaType) /// /// Gets the file extension of the given media type. /// - public static String ToFileExtension(Int32 mediaType) + public static string ToFileExtension(int mediaType) { if (registry.ContainsKey(mediaType)) { - return registry[mediaType][1]; + return registry[mediaType].ContentType[1]; } - else - { + else { return "unknown_" + mediaType; } } - public static Int32 NegotiationContent(Int32 defaultContentType, IEnumerable supported, IEnumerable