From a9ddcbb80345a379e7b40b11d3cb32efcb00ac3e Mon Sep 17 00:00:00 2001 From: Jim Schaad Date: Sat, 16 Nov 2019 04:24:34 +0800 Subject: [PATCH] Get group OSCORE roll over functionality in place Put the functionality in place for doing group rollover of groups. * Define a new class for OSCORE events * Put the event notification points for those events * Make the server security context set be set on the server and no longer global * Add functions to the SecurityContext for setting up group roll overs. --- CoAP.Example/CoAP.Client/ExampleClient.cs | 7 +- CoAP.NET/CoAP.Std10.csproj | 3 + CoAP.NET/DTLS/DTLSClientEndPoint.cs | 4 +- CoAP.NET/Net/CoAPEndPoint.cs | 47 +-- CoAP.NET/Net/Exchange.cs | 4 +- CoAP.NET/Net/IEndPoint.cs | 6 +- CoAP.NET/OSCOAP/OscoapLayer.cs | 326 +++++++++++++------- CoAP.NET/OSCOAP/OscoreEvent.cs | 35 +++ CoAP.NET/OSCOAP/SecureBlockwiseLayer.cs | 10 +- CoAP.NET/OSCOAP/SecurityContext.cs | 114 +++++-- CoAP.NET/OSCOAP/SecurityContextSet.cs | 47 ++- CoAP.NET/Server/CoapServer.cs | 9 +- CoAP.Test/OSCOAP/Callbacks.cs | 352 ++++++++++++++++++++++ CoAP.Test/OSCOAP/Oscoap.cs | 21 +- 14 files changed, 784 insertions(+), 201 deletions(-) create mode 100644 CoAP.NET/OSCOAP/OscoreEvent.cs create mode 100644 CoAP.Test/OSCOAP/Callbacks.cs diff --git a/CoAP.Example/CoAP.Client/ExampleClient.cs b/CoAP.Example/CoAP.Client/ExampleClient.cs index 240d1f7..48b0c9f 100644 --- a/CoAP.Example/CoAP.Client/ExampleClient.cs +++ b/CoAP.Example/CoAP.Client/ExampleClient.cs @@ -264,6 +264,7 @@ private static SecurityContextSet LoadContextSet(string fileName) { if (fileName == null) fileName = "ServerKeys.cbor"; KeySet keys = new KeySet(); + SecurityContextSet newSet = new SecurityContextSet(); FileStream fs = new FileStream(fileName, FileMode.Open); using (BinaryReader reader = new BinaryReader(fs)) { @@ -281,7 +282,7 @@ private static SecurityContextSet LoadContextSet(string fileName) key[CBORObject.FromObject("RecipID")].GetByteString(), key[CBORObject.FromObject("SenderID")].GetByteString(), null, key[CoseKeyKeys.Algorithm]); - SecurityContextSet.AllContexts.Add(ctx); + newSet.Add(ctx); break; } else if (usage == "oscoap-group") { @@ -292,7 +293,7 @@ private static SecurityContextSet LoadContextSet(string fileName) foreach (CBORObject recipient in key[CBORObject.FromObject("recipients")].Values) { ctx.AddRecipient(recipient[CBORObject.FromObject("RecipID")].GetByteString(), new OneKey( recipient[CBORObject.FromObject("sign")])); } - SecurityContextSet.AllContexts.Add(ctx); + newSet.Add(ctx); } } @@ -304,7 +305,7 @@ private static SecurityContextSet LoadContextSet(string fileName) } // - return SecurityContextSet.AllContexts; + return newSet; } } diff --git a/CoAP.NET/CoAP.Std10.csproj b/CoAP.NET/CoAP.Std10.csproj index 1d5c921..36f4212 100644 --- a/CoAP.NET/CoAP.Std10.csproj +++ b/CoAP.NET/CoAP.Std10.csproj @@ -25,6 +25,8 @@ It is intented primarily for research and verification work. 1.6 - Use cache key fields for matching blockwise transfers. - Some corrections for blockwise transfers over TCP + - Put in events to deal with OSCORE declared errors - IV exhaustion and unknown groups among others + - Move the global OSCORE security contexts to be server specific 1.5 - Update to use CBOR package 4.0.0 due to a security bug found. 1.4 @@ -192,6 +194,7 @@ It is intented primarily for research and verification work. + diff --git a/CoAP.NET/DTLS/DTLSClientEndPoint.cs b/CoAP.NET/DTLS/DTLSClientEndPoint.cs index 80f9167..bffd95e 100644 --- a/CoAP.NET/DTLS/DTLSClientEndPoint.cs +++ b/CoAP.NET/DTLS/DTLSClientEndPoint.cs @@ -126,8 +126,8 @@ private void OnTlsEvent(Object o, TlsEvent e) public KeySet CwtTrustKeySet { - get { return ((DTLSClientChannel) _channel).CwtTrustKeySet; } - set { ((DTLSClientChannel) _channel).CwtTrustKeySet = value; } + get { return ((DTLSClientChannel) dataChannel).CwtTrustKeySet; } + set { ((DTLSClientChannel) dataChannel).CwtTrustKeySet = value; } } } } diff --git a/CoAP.NET/Net/CoAPEndPoint.cs b/CoAP.NET/Net/CoAPEndPoint.cs index 80043fb..3dff032 100644 --- a/CoAP.NET/Net/CoAPEndPoint.cs +++ b/CoAP.NET/Net/CoAPEndPoint.cs @@ -15,6 +15,7 @@ using Com.AugustCellars.CoAP.Channel; using Com.AugustCellars.CoAP.Codec; using Com.AugustCellars.CoAP.Log; +using Com.AugustCellars.CoAP.OSCOAP; using Com.AugustCellars.CoAP.Stack; using Com.AugustCellars.CoAP.Threading; using DataReceivedEventArgs = Com.AugustCellars.CoAP.Channel.DataReceivedEventArgs; @@ -42,7 +43,7 @@ public class CoAPEndPoint : IEndPoint, IOutbox /// Message decoder object public delegate IMessageDecoder FindMessageDecoder(byte[] data); - protected readonly IChannel _channel; + protected readonly IChannel dataChannel; readonly CoapStack _coapStack; private IMessageDeliverer _deliverer; private readonly IMatcher _matcher; @@ -116,11 +117,11 @@ public CoAPEndPoint(System.Net.EndPoint localEndPoint, ICoapConfig config) /// public CoAPEndPoint(IChannel channel, ICoapConfig config) { - _channel = channel ?? throw new ArgumentNullException(nameof(channel)); + dataChannel = channel ?? throw new ArgumentNullException(nameof(channel)); Config = config; _matcher = new Matcher(config); _coapStack = new CoapStack(config); - _channel.DataReceived += ReceiveData; + dataChannel.DataReceived += ReceiveData; EndpointSchema = new []{"coap", "coap+udp"}; } @@ -155,6 +156,9 @@ public IMessageDeliverer MessageDeliverer get => _deliverer ?? (_deliverer = new ClientMessageDeliverer()); } + /// + public SecurityContextSet SecurityContexts { get; set; } + /// /// Return the message decoder to use with the end point /// @@ -186,7 +190,7 @@ public CoapStack Stack /// public bool AddMulticastAddress(IPEndPoint ep) { - return _channel.AddMulticastAddress(ep); + return dataChannel.AddMulticastAddress(ep); } #endif @@ -201,11 +205,11 @@ public void Start() Executor = Executors.Default; } - LocalEndPoint = _channel.LocalEndPoint; + LocalEndPoint = dataChannel.LocalEndPoint; try { _matcher.Start(); - _channel.Start(); - LocalEndPoint = _channel.LocalEndPoint; + dataChannel.Start(); + LocalEndPoint = dataChannel.LocalEndPoint; } catch { _Log.Warn(m => m("Cannot start endpoint at {0}", LocalEndPoint)); @@ -223,7 +227,7 @@ public void Stop() } _Log.Debug(m => m("Stopping endpoint bound to {0}", LocalEndPoint)); - _channel.Stop(); + dataChannel.Stop(); _matcher.Stop(); _matcher.Clear(); } @@ -241,7 +245,7 @@ public void Dispose() Stop(); } - _channel.Dispose(); + dataChannel.Dispose(); IDisposable d = _matcher as IDisposable; if (d != null) { d.Dispose(); @@ -302,7 +306,7 @@ private void ReceiveData(DataReceivedEventArgs e) Fire(SendingEmptyMessage, rst); - _channel.Send(Serialize(rst), e.Session, rst.Destination); + dataChannel.Send(Serialize(rst), e.Session, rst.Destination); _Log.Warn(m => m("Message format error caused by {0} and reset.", e.EndPoint)); } @@ -423,8 +427,8 @@ private void ReceiveData(DataReceivedEventArgs e) op2.IntValue = (int) op.Type; signal.AddOption(op2); - _channel.Send(Serialize(signal), e.Session, e.EndPoint); - _channel.Abort(e.Session); + dataChannel.Send(Serialize(signal), e.Session, e.EndPoint); + dataChannel.Abort(e.Session); break; } } @@ -433,7 +437,7 @@ private void ReceiveData(DataReceivedEventArgs e) case SignalCode.Ping: signal = new SignalMessage(SignalCode.Pong); signal.Token = message.Token; - _channel.Send(Serialize(signal), e.Session, e.EndPoint); + dataChannel.Send(Serialize(signal), e.Session, e.EndPoint); break; case SignalCode.Pong: @@ -441,11 +445,11 @@ private void ReceiveData(DataReceivedEventArgs e) break; case SignalCode.Release: - _channel.Release(e.Session); + dataChannel.Release(e.Session); break; case SignalCode.Abort: - _channel.Abort(e.Session); + dataChannel.Abort(e.Session); break; } } @@ -460,8 +464,9 @@ private void Reject(Message message) Fire(SendingEmptyMessage, rst); - if (!rst.IsCancelled) - _channel.Send(Serialize(rst), null /*message.Session*/, rst.Destination); + if (!rst.IsCancelled) { + dataChannel.Send(Serialize(rst), null /*message.Session*/, rst.Destination); + } } private Byte[] Serialize(EmptyMessage message) @@ -539,9 +544,9 @@ void IOutbox.SendRequest(Exchange exchange, Request request) if (!request.IsCancelled) { if (request.Session == null) { - request.Session = _channel.GetSession(request.Destination); + request.Session = dataChannel.GetSession(request.Destination); } - _channel.Send(Serialize(request), request.Session, request.Destination); + dataChannel.Send(Serialize(request), request.Session, request.Destination); } } @@ -552,7 +557,7 @@ void IOutbox.SendResponse(Exchange exchange, Response response) Fire(SendingResponse, response); if (!response.IsCancelled) { - _channel.Send(Serialize(response), response.Session, response.Destination); + dataChannel.Send(Serialize(response), response.Session, response.Destination); } } @@ -563,7 +568,7 @@ void IOutbox.SendEmptyMessage(Exchange exchange, EmptyMessage message) Fire(SendingEmptyMessage, message); if (!message.IsCancelled) { - _channel.Send(Serialize(message), exchange.Request.Session, message.Destination); + dataChannel.Send(Serialize(message), exchange.Request.Session, message.Destination); } } } diff --git a/CoAP.NET/Net/Exchange.cs b/CoAP.NET/Net/Exchange.cs index 712e6ce..9d0817f 100644 --- a/CoAP.NET/Net/Exchange.cs +++ b/CoAP.NET/Net/Exchange.cs @@ -105,7 +105,7 @@ public bool TimedOut /// Gets or sets the status of the security blockwise transfer of the request, /// or null in case of a normal transfer, /// - public BlockwiseStatus OSCOAP_RequestBlockStatus { get; set; } + public BlockwiseStatus OscoreRequestBlockStatus { get; set; } /// /// Gets or sets the status of the security blockwise transfer of the response, @@ -284,7 +284,7 @@ public override bool Equals(object obj) return false; } - return _id == other._id && object.Equals(_endpoint, other._endpoint); // && (_session == other._session); + return _id == other._id && Equals(_endpoint, other._endpoint); // && (_session == other._session); } /// diff --git a/CoAP.NET/Net/IEndPoint.cs b/CoAP.NET/Net/IEndPoint.cs index 912a6c1..b344dfa 100644 --- a/CoAP.NET/Net/IEndPoint.cs +++ b/CoAP.NET/Net/IEndPoint.cs @@ -11,7 +11,7 @@ using System; using System.Net; -using System.Reflection; +using Com.AugustCellars.CoAP.OSCOAP; namespace Com.AugustCellars.CoAP.Net { @@ -38,6 +38,10 @@ public interface IEndPoint : IDisposable /// IMessageDeliverer MessageDeliverer { get; set; } /// + /// Gets/sets the OSCORE contexts + /// + SecurityContextSet SecurityContexts { get; set; } + /// /// Gets the outbox. /// IOutbox Outbox { get; } diff --git a/CoAP.NET/OSCOAP/OscoapLayer.cs b/CoAP.NET/OSCOAP/OscoapLayer.cs index 79cc2b0..a82f919 100644 --- a/CoAP.NET/OSCOAP/OscoapLayer.cs +++ b/CoAP.NET/OSCOAP/OscoapLayer.cs @@ -8,6 +8,7 @@ using Com.AugustCellars.CoAP.Stack; using Com.AugustCellars.CoAP.Util; using Com.AugustCellars.COSE; +using Org.BouncyCastle.Utilities.Encoders; using PeterO.Cbor; using Attributes = Com.AugustCellars.COSE.Attributes; @@ -16,8 +17,8 @@ namespace Com.AugustCellars.CoAP.OSCOAP public class OscoapLayer : AbstractLayer { static readonly ILogger _Log = LogManager.GetLogger(typeof(OscoapLayer)); - static readonly byte[] _FixedHeader = new byte[] {0x40, 0x01, 0xff, 0xff}; - bool _replayWindow = true; + static readonly byte[] fixedHeader = new byte[] {0x40, 0x01, 0xff, 0xff}; + bool _replayWindow; readonly ConcurrentDictionary _ongoingExchanges = new ConcurrentDictionary(); class BlockHolder @@ -28,14 +29,14 @@ class BlockHolder public BlockHolder(Exchange exchange) { - _requestStatus = exchange.OSCOAP_RequestBlockStatus; + _requestStatus = exchange.OscoreRequestBlockStatus; _responseStatus = exchange.OSCOAP_ResponseBlockStatus; _response = exchange.Response; } public void RestoreTo(Exchange exchange) { - exchange.OSCOAP_RequestBlockStatus = _requestStatus; + exchange.OscoreRequestBlockStatus = _requestStatus; exchange.OSCOAP_ResponseBlockStatus = _responseStatus; exchange.Response = _response; } @@ -73,6 +74,20 @@ public override void SendRequest(INextLayer nextLayer, Exchange exchange, Reques exchange.OscoreContext = ctx; } + if (ctx.Sender.SequenceNumberExhausted) { + OscoreEvent e = new OscoreEvent(OscoreEvent.EventCode.PivExhaustion, null, null, ctx, ctx.Sender); + _Log.Info(m => m($"Partial IV exhaustion occured for {Base64.ToBase64String(ctx.Sender.Key)}")); + + ctx.OnEvent(e); + if (e.SecurityContext == ctx) { + throw new CoAPException("Kill message from IV exhaustion"); + } + + ctx = e.SecurityContext; + exchange.OscoreContext = ctx; + request.OscoreContext = ctx; + } + Codec.IMessageEncoder me = Spec.NewMessageEncoder(); Request encryptedRequest = new Request(request.Method); @@ -85,6 +100,10 @@ public override void SendRequest(INextLayer nextLayer, Exchange exchange, Reques _Log.Info(m => m("New inner response message\n{0}", encryptedRequest.ToString())); ctx.Sender.IncrementSequenceNumber(); + if (ctx.Sender.SendSequenceNumberUpdate) { + OscoreEvent e = new OscoreEvent(OscoreEvent.EventCode.SenderIvSave, null, null, ctx, ctx.Sender); + ctx.OnEvent(e); + } Encrypt0Message enc = new Encrypt0Message(false); byte[] msg = me.Encode(encryptedRequest); @@ -113,7 +132,7 @@ public override void SendRequest(INextLayer nextLayer, Exchange exchange, Reques aad.Add(CBORObject.FromObject(ctx.Sender.PartialIV)); aad.Add(CBORObject.FromObject(new byte[0])); // I options go here - _Log.Info(m => m("SendRequest: AAD = {0}", BitConverter.ToString(aad.EncodeToBytes()))); + _Log.Info(m => m($"SendRequest: AAD = {BitConverter.ToString(aad.EncodeToBytes())}")); enc.SetExternalData(aad.EncodeToBytes()); enc.AddAttribute(HeaderKeys.IV, ctx.Sender.GetIV(ctx.Sender.PartialIV), Attributes.DO_NOT_SEND); @@ -218,10 +237,19 @@ public override void ReceiveRequest(INextLayer nextLayer, Exchange exchange, Req } if (gid != null) { - contexts = SecurityContextSet.AllContexts.FindByGroupId(gid.GetByteString()); + contexts = exchange.EndPoint.SecurityContexts.FindByGroupId(gid.GetByteString()); + if (contexts.Count == 0) { + OscoreEvent e = new OscoreEvent(OscoreEvent.EventCode.UnknownGroupIdentifier, gid.GetByteString(), kid.GetByteString(), null, null); + + exchange.EndPoint.SecurityContexts.OnEvent(e); + + if (e.SecurityContext != null) { + contexts.Add(e.SecurityContext); + } + } } else { - contexts = SecurityContextSet.AllContexts.FindByKid(kid.GetByteString()); + contexts = exchange.EndPoint.SecurityContexts.FindByKid(kid.GetByteString()); } if (contexts.Count == 0) { @@ -253,108 +281,126 @@ public override void ReceiveRequest(INextLayer nextLayer, Exchange exchange, Req string responseString = "General decrypt failure"; - foreach (SecurityContext context in contexts) { - SecurityContext.EntityContext recip = context.Recipient; - if (gid != null) { - if (recip != null) { - if (!SecurityContext.ByteArrayComparer.AreEqual(recip.Id, kid.GetByteString())) { - continue; - } - } - else { - if (context.Recipients.ContainsKey(kid.GetByteString())) { - recip = context.Recipients[kid.GetByteString()]; + for (int pass = 0; pass < 2; pass++) { + // Don't try and get things fixed the first time around if more than one context exists.' + responseString = "General decrypt failure"; + if (contexts.Count == 1) { + pass = 1; + } + + foreach (SecurityContext context in contexts) { + SecurityContext.EntityContext recip = context.Recipient; + if (gid != null) { + if (recip != null) { + if (!SecurityContext.ByteArrayComparer.AreEqual(recip.Id, kid.GetByteString())) { + continue; + } } else { - if (context.Locate == null) { - continue; + if (context.Recipients.ContainsKey(kid.GetByteString())) { + recip = context.Recipients[kid.GetByteString()]; + } + else if (pass == 1) { + OscoreEvent e = new OscoreEvent(OscoreEvent.EventCode.UnknownKeyIdentifier, gid.GetByteString(), kid.GetByteString(), context, null); + context.OnEvent(e); + if (e.RecipientContext == null) { + continue; + } + + if (e.SecurityContext != context) { + continue; + } + recip = e.RecipientContext; } - recip = context.Locate(context, kid.GetByteString()); - if (recip == null) { + else { continue; } } } - } - - if (false && _replayWindow && recip.ReplayWindow.HitTest(seqNo)) { - _Log.Info(m => m("Hit test on {0} failed", seqNo)); - responseString = "Hit test - duplicate"; - continue; - } - else { - _Log.Info(m=>m("Hit test disabled")); - } - aad[1] = CBORObject.NewArray(); - aad[1].Add(recip.Algorithm); - if (context.Sender.SigningKey != null) { - aad[1].Add(context.Sender.SigningKey[CoseKeyKeys.Algorithm]); - if (context.CountersignParams != null) { - aad[1].Add(context.CountersignParams); + if (_replayWindow && recip.ReplayWindow.HitTest(seqNo)) { + _Log.Info(m => m("Hit test on {0} failed", seqNo)); + responseString = "Hit test - duplicate"; + continue; } - - if (context.CountersignKeyParams != null) { - aad[1].Add(context.CountersignKeyParams); + else { + _Log.Info(m => m("Hit test disabled")); } - int cbSignature = context.SignatureSize; - byte[] rgbSignature = new byte[cbSignature]; - byte[] rgbPayload = new byte[request.Payload.Length - cbSignature]; + aad[1] = CBORObject.NewArray(); + aad[1].Add(recip.Algorithm); + if (context.Sender.SigningKey != null) { + aad[1].Add(context.Sender.SigningKey[CoseKeyKeys.Algorithm]); + if (context.CountersignParams != null) { + aad[1].Add(context.CountersignParams); + } - Array.Copy(request.Payload, rgbPayload, rgbPayload.Length); - Array.Copy(request.Payload, rgbPayload.Length, rgbSignature, 0, cbSignature); + if (context.CountersignKeyParams != null) { + aad[1].Add(context.CountersignKeyParams); + } - CounterSignature1 cs1 = new CounterSignature1(rgbSignature); - cs1.AddAttribute(HeaderKeys.Algorithm, context.Sender.SigningAlgorithm, Attributes.DO_NOT_SEND); - cs1.SetObject(msg); - cs1.SetKey(recip.SigningKey); + int cbSignature = context.SignatureSize; + byte[] rgbSignature = new byte[cbSignature]; + byte[] rgbPayload = new byte[request.Payload.Length - cbSignature]; - aad.Add(op.RawValue); - byte[] aadData = aad.EncodeToBytes(); - cs1.SetExternalData(aadData); - msg.SetEncryptedContent(rgbPayload); + Array.Copy(request.Payload, rgbPayload, rgbPayload.Length); + Array.Copy(request.Payload, rgbPayload.Length, rgbSignature, 0, cbSignature); - try { - if (!msg.Validate(cs1)) { + CounterSignature1 cs1 = new CounterSignature1(rgbSignature); + cs1.AddAttribute(HeaderKeys.Algorithm, context.Sender.SigningAlgorithm, Attributes.DO_NOT_SEND); + cs1.SetObject(msg); + cs1.SetKey(recip.SigningKey); + + aad.Add(op.RawValue); + byte[] aadData = aad.EncodeToBytes(); + cs1.SetExternalData(aadData); + msg.SetEncryptedContent(rgbPayload); + + try { + if (!msg.Validate(cs1)) { + continue; + } + + } + catch (CoseException) { + // try the next possible one continue; } } - catch (CoseException e) { - // try the next possible one - continue; - } - } + if (aad.Count == 6) { + aad.Remove(aad[5]); + } - if (aad.Count == 6) { - aad.Remove(aad[5]); - } + if (_Log.IsInfoEnabled) { + _Log.Info("AAD = " + BitConverter.ToString(aad.EncodeToBytes())); + _Log.Info("IV = " + BitConverter.ToString(recip.GetIV(partialIV).GetByteString())); + _Log.Info("Key = " + BitConverter.ToString(recip.Key)); + } - if (_Log.IsInfoEnabled) { - _Log.Info("AAD = " + BitConverter.ToString(aad.EncodeToBytes())); - _Log.Info("IV = " + BitConverter.ToString(recip.GetIV(partialIV).GetByteString())); - _Log.Info("Key = " + BitConverter.ToString(recip.Key)); - } + msg.SetExternalData(aad.EncodeToBytes()); - msg.SetExternalData(aad.EncodeToBytes()); + msg.AddAttribute(HeaderKeys.Algorithm, recip.Algorithm, Attributes.DO_NOT_SEND); + msg.AddAttribute(HeaderKeys.IV, recip.GetIV(partialIV), Attributes.DO_NOT_SEND); - msg.AddAttribute(HeaderKeys.Algorithm, recip.Algorithm, Attributes.DO_NOT_SEND); - msg.AddAttribute(HeaderKeys.IV, recip.GetIV(partialIV), Attributes.DO_NOT_SEND); + try { + ctx = context; + payload = msg.Decrypt(recip.Key); + if (recip.ReplayWindow.SetHit(seqNo)) { + OscoreEvent e = new OscoreEvent(OscoreEvent.EventCode.HitZoneMoved, null, null, ctx, recip); + context.OnEvent(e); + } - try { - ctx = context; - payload = msg.Decrypt(recip.Key); - recip.ReplayWindow.SetHit(seqNo); - } - catch (Exception e) { - if (_Log.IsInfoEnabled) _Log.Info("--- ", e); - ctx = null; - } + } + catch (Exception e) { + if (_Log.IsInfoEnabled) _Log.Info("--- ", e); + ctx = null; + } - if (ctx != null) { - break; + if (ctx != null) { + break; + } } } @@ -373,9 +419,9 @@ public override void ReceiveRequest(INextLayer nextLayer, Exchange exchange, Req exchange.OscoapSequenceNumber = partialIV; exchange.OscoapSenderId = kid.GetByteString(); - byte[] newRequestData = new byte[payload.Length + _FixedHeader.Length-1]; - Array.Copy(_FixedHeader, newRequestData, _FixedHeader.Length); - Array.Copy(payload, 1, newRequestData, _FixedHeader.Length, payload.Length-1); + byte[] newRequestData = new byte[payload.Length + fixedHeader.Length-1]; + Array.Copy(fixedHeader, newRequestData, fixedHeader.Length); + Array.Copy(payload, 1, newRequestData, fixedHeader.Length, payload.Length-1); newRequestData[1] = payload[0]; Codec.IMessageDecoder me = Spec.NewMessageDecoder(newRequestData); @@ -426,6 +472,28 @@ public override void SendResponse(INextLayer nextLayer, Exchange exchange, Respo if (exchange.OscoreContext != null) { SecurityContext ctx = exchange.OscoreContext; + if (ctx.ReplaceWithSecurityContext != null) { + while (ctx.ReplaceWithSecurityContext != null) { + ctx = ctx.ReplaceWithSecurityContext; + } + + exchange.OscoreContext = ctx; + } + + if (ctx.Sender.SequenceNumberExhausted && response.HasOption(OptionType.Observe)) { + OscoreEvent e = new OscoreEvent(OscoreEvent.EventCode.PivExhaustion, null, null, ctx, ctx.Sender); + _Log.Info(m => m($"Partial IV exhaustion occured for {Base64.ToBase64String(ctx.Sender.Key)}")); + + ctx.OnEvent(e); + ctx = e.SecurityContext; + if (ctx.Sender.SequenceNumberExhausted) { + throw new CoAPException("Kill message from IV exhaustion"); + } + + ctx = e.SecurityContext; + exchange.OscoreContext = ctx; + } + Codec.IMessageEncoder me = Spec.NewMessageEncoder(); Response encryptedResponse = new Response((StatusCode) response.Code); @@ -477,6 +545,10 @@ public override void SendResponse(INextLayer nextLayer, Exchange exchange, Respo Attributes.UNPROTECTED); enc.AddAttribute(HeaderKeys.IV, ctx.Sender.GetIV(ctx.Sender.PartialIV), Attributes.DO_NOT_SEND); ctx.Sender.IncrementSequenceNumber(); + if (ctx.Sender.SendSequenceNumberUpdate) { + OscoreEvent e = new OscoreEvent(OscoreEvent.EventCode.SenderIvSave, null, null, ctx, ctx.Sender); + ctx.OnEvent(e); + } if (ctx.IsGroupContext) { enc.AddAttribute(HeaderKeys.KeyId, CBORObject.FromObject(ctx.Sender.Id), Attributes.UNPROTECTED); @@ -543,18 +615,14 @@ public override void SendResponse(INextLayer nextLayer, Exchange exchange, Respo // Remember ongoing blockwise GET requests BlockHolder blockInfo = new BlockHolder(exchange); if (Utils.Put(_ongoingExchanges, keyUri, blockInfo) == null) { - if (_Log.IsInfoEnabled) - _Log.Info("Ongoing Block2 started late, storing " + keyUri + " for " + request); - + _Log.Info($"Ongoing Block2 started late, storing {keyUri} for {request}"); } - else { - if (_Log.IsInfoEnabled) - _Log.Info("Ongoing Block2 continued, storing " + keyUri + " for " + request); + else { + _Log.Info($"Ongoing Block2 continued, storing {keyUri} for {request}"); } } - else { - if (_Log.IsInfoEnabled) - _Log.Info("Ongoing Block2 completed, cleaning up " + keyUri + " for " + request); + else { + _Log.Info($"Ongoing Block2 completed, cleaning up {keyUri} for {request}"); BlockHolder exc; _ongoingExchanges.TryRemove(keyUri, out exc); } @@ -591,14 +659,38 @@ public override void ReceiveResponse(INextLayer nextLayer, Exchange exchange, Re } CBORObject kid = msg.FindAttribute(HeaderKeys.KeyId); - if (kid == null) { + if (kid == null) + { // this is not currently a valid state to be in return; } - recip = ctx.Recipients[kid.GetByteString()]; + + CBORObject gid = msg.FindAttribute(HeaderKeys.KidContext); + if (gid != null && !SecurityContext.ByteArrayComparer.AreEqual(ctx.GroupId, gid.GetByteString())) { + OscoreEvent e = new OscoreEvent(OscoreEvent.EventCode.UnknownGroupIdentifier, gid.GetByteString(), kid.GetByteString(), null, null); + ctx.OnEvent(e); + + if (e.SecurityContext == null) { + return; + } + + ctx = e.SecurityContext; + } + + if (gid == null) { + gid = CBORObject.FromObject(ctx.GroupId); + } + + ctx.Recipients.TryGetValue(kid.GetByteString(), out recip); if (recip == null) { - // M00TODO - deal with asking the user for a recipient structure at this point. - return; + OscoreEvent e = new OscoreEvent(OscoreEvent.EventCode.UnknownKeyIdentifier, gid.GetByteString(), kid.GetByteString(), ctx, null); + ctx.OnEvent(e); + + if (e.RecipientContext == null) { + return; + } + + recip = e.RecipientContext; } if (msg.FindAttribute(HeaderKeys.PartialIV) == null) { @@ -687,7 +779,7 @@ public override void ReceiveResponse(INextLayer nextLayer, Exchange exchange, Re } } - catch (CoseException e) { + catch (CoseException) { // try the next possible one return; } @@ -695,11 +787,14 @@ public override void ReceiveResponse(INextLayer nextLayer, Exchange exchange, Re byte[] payload = msg.Decrypt(recip.Key); - recip.ReplayWindow.SetHit(seqNo); + if (recip.ReplayWindow.SetHit(seqNo)) { + OscoreEvent e = new OscoreEvent(OscoreEvent.EventCode.HitZoneMoved, null, null, ctx, recip); + ctx.OnEvent(e); + } - byte[] rgb = new byte[payload.Length + _FixedHeader.Length - 1]; - Array.Copy(_FixedHeader, rgb, _FixedHeader.Length); - Array.Copy(payload, 1, rgb, _FixedHeader.Length, payload.Length-1); + byte[] rgb = new byte[payload.Length + fixedHeader.Length - 1]; + Array.Copy(fixedHeader, rgb, fixedHeader.Length); + Array.Copy(payload, 1, rgb, fixedHeader.Length, payload.Length-1); rgb[1] = payload[0]; Codec.IMessageDecoder me = Spec.NewMessageDecoder(rgb); Response decryptedReq = me.DecodeResponse(); @@ -765,14 +860,10 @@ private static void MoveRequestHeaders(Request unprotected, Request encrypted) } string p = unprotected.ProxyUri.AbsolutePath; - if (p != null) { - encrypted.UriPath = p; - } + encrypted.UriPath = p; - if (unprotected.ProxyUri.Query != null) { - encrypted.AddUriQuery(unprotected.ProxyUri.Query); - unprotected.ClearUriQuery(); - } + encrypted.AddUriQuery(unprotected.ProxyUri.Query); + unprotected.ClearUriQuery(); unprotected.URI = new Uri(strUri + "/"); } @@ -802,7 +893,10 @@ private static void MoveRequestHeaders(Request unprotected, Request encrypted) } } - foreach (Option op in toDelete) unprotected.RemoveOptions(op.Type); + foreach (Option op in toDelete) { + unprotected.RemoveOptions(op.Type); + } + unprotected.URI = null; } @@ -837,7 +931,9 @@ private static void MoveResponseHeaders(Response unprotected, Response encrypted } } - foreach (Option op in toDelete) unprotected.RemoveOptions(op.Type); + foreach (Option op in toDelete) { + unprotected.RemoveOptions(op.Type); + } } private static void RestoreOptions(Message response, Message decryptedReq) @@ -866,7 +962,9 @@ private static void RestoreOptions(Message response, Message decryptedReq) } } - foreach (Option op in toDelete) response.RemoveOptions(op.Type); + foreach (Option op in toDelete) { + response.RemoveOptions(op.Type); + } foreach (Option op in decryptedReq.GetOptions()) { switch (op.Type) { diff --git a/CoAP.NET/OSCOAP/OscoreEvent.cs b/CoAP.NET/OSCOAP/OscoreEvent.cs new file mode 100644 index 0000000..467c29b --- /dev/null +++ b/CoAP.NET/OSCOAP/OscoreEvent.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Com.AugustCellars.COSE; + +namespace Com.AugustCellars.CoAP.OSCOAP +{ + public class OscoreEvent + { + public enum EventCode + { + UnknownGroupIdentifier = 1, + UnknownKeyIdentifier = 2, + UnknownPublicKey = 3, + PivExhaustion = 4, + HitZoneMoved = 5, + SenderIvSave = 6 + } + + public EventCode Code { get; } + public byte[] GroupIdentifier { get; } + public byte[] KeyIdentifier { get; } + public SecurityContext SecurityContext { get; set; } + public SecurityContext.EntityContext RecipientContext { get; set; } + + public OscoreEvent(EventCode code, byte[] groupIdentifier, byte[] keyIdentifier, SecurityContext context, SecurityContext.EntityContext recipient) + { + Code = code; + GroupIdentifier = groupIdentifier; + KeyIdentifier = keyIdentifier; + SecurityContext = context; + RecipientContext = recipient; + } + } +} diff --git a/CoAP.NET/OSCOAP/SecureBlockwiseLayer.cs b/CoAP.NET/OSCOAP/SecureBlockwiseLayer.cs index b27a3b7..f436cd7 100644 --- a/CoAP.NET/OSCOAP/SecureBlockwiseLayer.cs +++ b/CoAP.NET/OSCOAP/SecureBlockwiseLayer.cs @@ -73,7 +73,7 @@ public override void SendRequest(INextLayer nextLayer, Exchange exchange, Reques log.Debug("Request payload " + request.PayloadSize + "/" + _maxMessageSize + " requires Blockwise."); BlockwiseStatus status = FindRequestBlockStatus(exchange, request); Request block = GetNextRequestBlock(request, status); - exchange.OSCOAP_RequestBlockStatus = status; + exchange.OscoreRequestBlockStatus = status; exchange.CurrentRequest = block; base.SendRequest(nextLayer, exchange, block); } @@ -105,7 +105,7 @@ public override void ReceiveRequest(INextLayer nextLayer, Exchange exchange, Req if (log.IsDebugEnabled) log.Debug("Block1 num is 0, the client has restarted the blockwise transfer. Reset status."); status = new BlockwiseStatus(request.ContentType); - exchange.OSCOAP_RequestBlockStatus = status; + exchange.OscoreRequestBlockStatus = status; } if (block1.NUM == status.CurrentNUM) @@ -306,7 +306,7 @@ public override void ReceiveResponse(INextLayer nextLayer, Exchange exchange, Re if (log.IsDebugEnabled) log.Debug("Response acknowledges block " + block1); - BlockwiseStatus status = exchange.OSCOAP_RequestBlockStatus; + BlockwiseStatus status = exchange.OscoreRequestBlockStatus; if (!status.Complete) { // TODO: the response code should be CONTINUE. Otherwise deliver @@ -451,12 +451,12 @@ private void EarlyBlock2Negotiation(Exchange exchange, Request request) /// private BlockwiseStatus FindRequestBlockStatus(Exchange exchange, Request request) { - BlockwiseStatus status = exchange.OSCOAP_RequestBlockStatus; + BlockwiseStatus status = exchange.OscoreRequestBlockStatus; if (status == null) { status = new BlockwiseStatus(request.ContentType); status.CurrentSZX = BlockOption.EncodeSZX(_defaultBlockSize); - exchange.OSCOAP_RequestBlockStatus = status; + exchange.OscoreRequestBlockStatus = status; if (log.IsDebugEnabled) log.Debug("There is no assembler status yet. Create and set new Block1 status: " + status); } diff --git a/CoAP.NET/OSCOAP/SecurityContext.cs b/CoAP.NET/OSCOAP/SecurityContext.cs index b36414d..e357113 100644 --- a/CoAP.NET/OSCOAP/SecurityContext.cs +++ b/CoAP.NET/OSCOAP/SecurityContext.cs @@ -55,11 +55,14 @@ public bool HitTest(long index) /// Set a value has having been seen. /// /// value that was seen - public void SetHit(Int64 index) + /// true if the zone was shifted + public bool SetHit(long index) { + bool returnValue = false; index -= BaseValue; - if (index < 0) return; + if (index < 0) return false; if (index > _hits.Length) { + returnValue = true; if (index < _hits.Length * 3 / 2) { int v = _hits.Length / 2; BaseValue += v; @@ -78,6 +81,7 @@ public void SetHit(Int64 index) } } _hits.Set((int) index, true); + return returnValue; } } #endregion @@ -137,6 +141,17 @@ public EntityContext(EntityContext old) /// public int SequenceNumber { get; set; } + /// + /// At what frequency should the IV update event be sent? + /// SequenceNumber % SequenceInterval == 0 + /// + public int SequenceInterval { get; set; } = 100; + + /// + /// Should an IV update event be sent? + /// + public bool SendSequenceNumberUpdate => (SequenceNumber % SequenceInterval) == 0; + /// /// Return the sequence number as a byte array. /// @@ -193,7 +208,34 @@ public CBORObject GetIV(byte[] partialIV) /// /// Increment the sequence/parital IV /// - public void IncrementSequenceNumber() { SequenceNumber += 1; } + public void IncrementSequenceNumber() + { + SequenceNumber += 1; + if (SequenceNumber > MaxSequenceNumber) { + throw new CoAPException("Oscore Partial IV exhaustion"); + } + } + + /// + /// Check to see if all of the Partial IV Sequence numbers are exhausted. + /// + /// true if exhausted + public bool SequenceNumberExhausted => SequenceNumber >= MaxSequenceNumber; + + private int _maxSequenceNumber = 0x1f; + /// + /// Set/get the maximum sequence number. Limited to five bits. + /// + public int MaxSequenceNumber + { + get => _maxSequenceNumber; + set { + if (value > 0x1f || value < 0) { + throw new CoAPException("value must be no more than 0x1f"); + } + _maxSequenceNumber = value; + } + } /// /// The key to use for counter signing purposes @@ -205,7 +247,7 @@ public override string ToString() { string ret = $"kid= {BitConverter.ToString(Id)} key={BitConverter.ToString(Key)} IV={BitConverter.ToString(BaseIV)} PartialIV={BitConverter.ToString(PartialIV)}\n"; if (SigningKey != null) { - ret += $" {SigningKey.AsCBOR().ToString()}"; + ret += $" {SigningKey.AsCBOR()}"; } return ret; @@ -213,8 +255,6 @@ public override string ToString() } #endregion - - private static int _contextNumber; private byte[] _masterSecret; private byte[] _salt; @@ -222,9 +262,6 @@ public override string ToString() public CBORObject CountersignKeyParams { get; set; } public int SignatureSize { get; } = 64; - public Func Locate { get; set; } - - /// /// What is the global unique context number for this context. /// @@ -255,7 +292,12 @@ public override string ToString() /// For contexts that are created by the system this will be a list of /// COSE Web Tokens for authorization /// - public Object UserData { get; set; } + public object UserData { get; set; } + + /// + /// Mark this context as being replaced with a new context + /// + public SecurityContext ReplaceWithSecurityContext { get; set; } /// /// Create a new empty security context @@ -266,6 +308,7 @@ public SecurityContext() { } /// Create a new security context to hold info for group. /// /// + [Obsolete ("Unused Constructor")] public SecurityContext(byte[] groupId) { Recipients = new Dictionary(new ByteArrayComparer()); @@ -303,9 +346,23 @@ public void AddRecipient(byte[] recipientId, OneKey signKey) Recipients.Add(recipientId, x); } + public void ReplaceSender(byte[] senderId, OneKey signKey) + { + if (!signKey.HasAlgorithm(Sender.SigningAlgorithm)) { + throw new ArgumentException("signature algorithm not correct"); + } + + EntityContext x = DeriveEntityContext(_masterSecret, GroupId, senderId, _salt, Sender.Algorithm); + x.SigningKey = signKey; + x.SigningAlgorithm = Sender.SigningAlgorithm; + + Sender = x; + } + + #region Key Derivation Functions /// - /// Given the set of inputs, perform the crptographic operations that are needed + /// Given the set of inputs, perform the cryptographic operations that are needed /// to build a security context for a single sender and recipient. /// /// pre-shared key @@ -433,7 +490,7 @@ public static SecurityContext DeriveContext(byte[] masterSecret, byte[] senderCo } /// - /// Given the set of inputs, perform the crptographic operations that are needed + /// Given the set of inputs, perform the cryptographic operations that are needed /// to build a security context for a single sender and recipient. /// /// pre-shared key @@ -451,10 +508,11 @@ public static SecurityContext DeriveGroupContext(byte[] masterSecret, byte[] gro byte[][] recipientIds, OneKey[] recipientSignKeys, byte[] masterSalt = null, CBORObject algAEAD = null, CBORObject algKeyAgree = null) { - SecurityContext ctx = new SecurityContext(); - ctx.Recipients = new Dictionary(new ByteArrayComparer()); - ctx._masterSecret = masterSecret; - ctx._salt = masterSalt; + SecurityContext ctx = new SecurityContext { + Recipients = new Dictionary(new ByteArrayComparer()), + _masterSecret = masterSecret, + _salt = masterSalt + }; if ((recipientIds != null && recipientSignKeys != null) && (recipientIds.Length != recipientSignKeys.Length)) { throw new ArgumentException("recipientsIds and recipientSignKey must be the same length"); @@ -491,7 +549,7 @@ public static SecurityContext DeriveGroupContext(byte[] masterSecret, byte[] gro /// - /// Given the set of inputs, perform the crptographic operations that are needed + /// Given the set of inputs, perform the cryptographic operations that are needed /// to build a security context for a single sender and recipient. /// /// pre-shared key @@ -576,33 +634,39 @@ private static EntityContext DeriveEntityContext(byte[] masterSecret, byte[] gro return ctx; } - +#endregion public bool IsGroupContext => Recipients != null; -#if DEBUG - static public int FutzError { get; set; } -#endif + public event EventHandler OscoreEvents; + + public void OnEvent(OscoreEvent e) + { + EventHandler eventHandler = OscoreEvents; + eventHandler?.Invoke(this, e); + } /// public override string ToString() { StringBuilder sb = new StringBuilder("SecurityContext: "); sb.Append($"Secret: {BitConverter.ToString(_masterSecret)}\n"); - sb.Append($"Sender: {Sender.ToString()}"); + sb.Append($"Sender: {Sender}"); if (IsGroupContext) { foreach (KeyValuePair entity in Recipients) { - sb.Append($"Entity: {entity.Value.ToString()}\n"); + sb.Append($"Entity: {entity.Value}\n"); } } else { - sb.Append($"Recipient: {Recipient.ToString()}"); + sb.Append($"Recipient: {Recipient}"); } return sb.ToString(); } +#region Equality comparer for bytes + public class ByteArrayComparer : EqualityComparer { public override bool Equals(byte[] first, byte[] second) @@ -636,5 +700,7 @@ public override int GetHashCode(byte[] obj) return obj.Length; } } +#endregion + } } diff --git a/CoAP.NET/OSCOAP/SecurityContextSet.cs b/CoAP.NET/OSCOAP/SecurityContextSet.cs index 139f2c5..4d0bca9 100644 --- a/CoAP.NET/OSCOAP/SecurityContextSet.cs +++ b/CoAP.NET/OSCOAP/SecurityContextSet.cs @@ -1,22 +1,18 @@ -using System.Collections.Generic; +using System; +using System.Collections; +using System.Collections.Generic; namespace Com.AugustCellars.CoAP.OSCOAP { /// /// Collection of OSCOAP security contexts /// - public class SecurityContextSet + public class SecurityContextSet : IEnumerable { - /// - /// Collection of all OSCOAP security contexts on the system. - /// - - public static SecurityContextSet AllContexts = new SecurityContextSet(); - /// /// Get the count of all security contexts. /// - public int Count { get => All.Count;} + public int Count => All.Count; /// /// Add a new context to the set @@ -27,8 +23,15 @@ public void Add(SecurityContext ctx) All.Add(ctx); } + public void Add(SecurityContextSet set) + { + foreach (SecurityContext c in set) { + All.Add(c); + } + } + /// - /// Secuirty contexts for the object + /// Security contexts for the object /// public List All { get; } = new List(); @@ -43,10 +46,13 @@ public List FindByKid(byte[] kid) foreach (SecurityContext ctx in All) { if (ctx.Recipient != null && kid.Length == ctx.Recipient.Id.Length) { bool match = true; - for (int i=0; i FindByGroupId(byte[] groupId) } return contexts; } + + + public event EventHandler OscoreEvents; + + public void OnEvent(OscoreEvent e) + { + EventHandler eventHandler = OscoreEvents; + eventHandler?.Invoke(null, e); + } + + public IEnumerator GetEnumerator() + { + return All.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return All.GetEnumerator(); + } } } diff --git a/CoAP.NET/Server/CoapServer.cs b/CoAP.NET/Server/CoapServer.cs index 896ac6e..9198a53 100644 --- a/CoAP.NET/Server/CoapServer.cs +++ b/CoAP.NET/Server/CoapServer.cs @@ -14,6 +14,7 @@ using System.Net; using Com.AugustCellars.CoAP.Log; using Com.AugustCellars.CoAP.Net; +using Com.AugustCellars.CoAP.OSCOAP; using Com.AugustCellars.CoAP.Server.Resources; namespace Com.AugustCellars.CoAP.Server @@ -29,6 +30,8 @@ public class CoapServer : IServer private readonly System.Net.EndPoint _endPointSupplied; private IMessageDeliverer _deliverer; + public SecurityContextSet SecurityContexts { get; } = new SecurityContextSet(); + /// /// Constructs a server with default configuration. /// @@ -141,6 +144,7 @@ public IMessageDeliverer MessageDeliverer public void AddEndPoint(IEndPoint endpoint) { endpoint.MessageDeliverer = _deliverer; + endpoint.SecurityContexts = SecurityContexts; _endpoints.Add(endpoint); } @@ -310,14 +314,13 @@ class RootResource : Resource { public RootResource(CoapServer server) - : base(String.Empty) + : base(string.Empty) { } protected override void DoGet(CoapExchange exchange) { - - exchange.Respond("Ni Hao from CoAP.NET " + Spec.Name); + exchange.Respond("Ni Hao from Com.AugustCellars.CoAP " + Spec.Name); } } } diff --git a/CoAP.Test/OSCOAP/Callbacks.cs b/CoAP.Test/OSCOAP/Callbacks.cs new file mode 100644 index 0000000..2007f44 --- /dev/null +++ b/CoAP.Test/OSCOAP/Callbacks.cs @@ -0,0 +1,352 @@ +using System; +using System.Text; +using System.Threading; +using Com.AugustCellars.CoAP; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Com.AugustCellars.CoAP.Server; +using Com.AugustCellars.CoAP.Net; +using Com.AugustCellars.CoAP.Log; +using Com.AugustCellars.CoAP.OSCOAP; +using Com.AugustCellars.CoAP.Server.Resources; +using Com.AugustCellars.COSE; + +namespace CoAP.Test.Std10.OSCOAP +{ + [TestClass] + public class Callbacks + { + private int _serverPort; + private CoapServer _server; + private OscoreEvent.EventCode _callbackCode; + private static readonly byte[] clientId = Encoding.UTF8.GetBytes("client"); + private static readonly byte[] clientId2 = Encoding.UTF8.GetBytes("client2"); + private static readonly byte[] serverId = Encoding.UTF8.GetBytes("server"); + private static readonly byte[] serverId2 = Encoding.UTF8.GetBytes("server2"); + private static readonly byte[] groupId1 = Encoding.UTF8.GetBytes("Group1"); + private static readonly byte[] groupId2 = Encoding.UTF8.GetBytes("Group2"); + private static readonly byte[] secret = new byte[] { 01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23 }; + private static readonly byte[] secret2 = new byte[] { 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24 }; + + private static OneKey _clientSign1; + private static OneKey _clientSign2; + private static OneKey _serverSign1; + + [ClassInitialize] + public static void ClassInit(TestContext e) + { + _clientSign1 = OneKey.GenerateKey(AlgorithmValues.EdDSA, GeneralValues.KeyType_OKP); + _clientSign2 = OneKey.GenerateKey(AlgorithmValues.EdDSA, GeneralValues.KeyType_OKP); + _serverSign1 = OneKey.GenerateKey(AlgorithmValues.EdDSA, GeneralValues.KeyType_OKP); + } + + [TestInitialize] + public void SetupServer() + { + LogManager.Level = LogLevel.Fatal; + CreateServer(); + } + + [TestCleanup] + public void ShutdownServer() + { + _server.Stop(); + _server.Dispose(); + } + + private int _clientEventChoice; + private OscoreEvent.EventCode _clientCallbackCode; + private void ClientEventHandler(object o, OscoreEvent e) + { + _clientCallbackCode = e.Code; + switch (_clientEventChoice) { + case 0: + _callbackCode = e.Code; + break; + + case 1: + _callbackCode = e.Code; + e.SecurityContext = SecurityContext.DeriveGroupContext(secret2, groupId2, clientId, AlgorithmValues.EdDSA, _clientSign1, null, null); + break; + + default: + Assert.Fail(); + break; + } + } + + + [TestMethod] + public void PivExhaustion() + { + SecurityContext context = SecurityContext.DeriveGroupContext(secret, groupId1, clientId, AlgorithmValues.EdDSA, _clientSign1, null, null); + SecurityContext context2 = SecurityContext.DeriveGroupContext(secret2, groupId2, clientId, AlgorithmValues.EdDSA, _clientSign1, null, null); + for (int i = 0; i < 10; i++) { + context.Sender.IncrementSequenceNumber(); + } + + context.Sender.MaxSequenceNumber = 10; + context.OscoreEvents += ClientEventHandler; + + CoapClient client = new CoapClient($"coap://localhost:{_serverPort}/abc") { + OscoreContext = context, + Timeout = 20 + }; + + Response r = client.Get(); + + Assert.AreEqual(OscoreEvent.EventCode.PivExhaustion, _clientCallbackCode); + + _clientEventChoice = 1; + client.Timeout = 1000 * 60; + r = client.Get(); + Assert.AreEqual(OscoreEvent.EventCode.UnknownGroupIdentifier, _callbackCode); + + } + + [TestMethod] + public void NoGroupId() + { + SecurityContext context = SecurityContext.DeriveGroupContext(secret, groupId1, clientId, AlgorithmValues.EdDSA, _clientSign1, null, null); + + CoapClient client = new CoapClient($"coap://localhost:{_serverPort}/abc") { + OscoreContext = context, +// Timeout = 60 + }; + Console.WriteLine($"--Server port = {_serverPort}"); + + + Response r = client.Get(); + Assert.AreEqual(OscoreEvent.EventCode.UnknownGroupIdentifier, _callbackCode); + } + + [TestMethod] + public void SetGroupId() + { + SecurityContext context = SecurityContext.DeriveGroupContext(secret, groupId1, clientId, AlgorithmValues.EdDSA, _clientSign1, + new byte[][]{serverId}, new OneKey[]{_serverSign1}); + + CoapClient client = new CoapClient($"coap://localhost:{_serverPort}/abc") { + OscoreContext = context, + Timeout = 60*1000 + }; + Console.WriteLine($"--Server port = {_serverPort}"); + + _serverEventChoice = 1; + Response r = client.Get(); + Assert.AreEqual("/abc", r.PayloadString); + } + + [TestMethod] + public void MissingKeyId() + { + SecurityContext context = SecurityContext.DeriveGroupContext(secret, groupId1, clientId, AlgorithmValues.EdDSA, _clientSign1, + new byte[][] { serverId }, new OneKey[] { _serverSign1 }); + SecurityContext serverContext = SecurityContext.DeriveGroupContext(secret, groupId1, serverId, AlgorithmValues.EdDSA, _serverSign1, + new byte[][]{clientId2}, new OneKey[]{_clientSign2}); + _server.SecurityContexts.Add(serverContext); + serverContext.OscoreEvents += ServerEventHandler; + + CoapClient client = new CoapClient($"coap://localhost:{_serverPort}/abc") + { + OscoreContext = context, + Timeout = 60 * 1000 + }; + + _serverEventChoice = 0; + Response r = client.Get(); + Assert.AreEqual(OscoreEvent.EventCode.UnknownKeyIdentifier, _callbackCode); + } + + [TestMethod] + public void SupplyMissingKeyId() + { + SecurityContext context = SecurityContext.DeriveGroupContext(secret, groupId1, clientId, AlgorithmValues.EdDSA, _clientSign1, + new byte[][] { serverId }, new OneKey[] { _serverSign1 }); + SecurityContext serverContext = SecurityContext.DeriveGroupContext(secret, groupId1, serverId, AlgorithmValues.EdDSA, _serverSign1, + new byte[][] { clientId2 }, new OneKey[] { _clientSign2 }); + _server.SecurityContexts.Add(serverContext); + serverContext.OscoreEvents += ServerEventHandler; + + CoapClient client = new CoapClient($"coap://localhost:{_serverPort}/abc") + { + OscoreContext = context, + Timeout = 60 * 1000 + }; + + _serverEventChoice = 2; + Response r = client.Get(); + + Assert.AreEqual("/abc", r.PayloadString); + + } + + [TestMethod] + public void ServerIvExhaustion() + { + SecurityContext context = SecurityContext.DeriveGroupContext(secret, groupId1, clientId, AlgorithmValues.EdDSA, _clientSign1, + new byte[][] { serverId }, new OneKey[] { _serverSign1 }); + SecurityContext serverContext = SecurityContext.DeriveGroupContext(secret, groupId1, serverId, AlgorithmValues.EdDSA, _serverSign1, + new byte[][] { clientId2, clientId }, new OneKey[] { _clientSign2, _clientSign1 }); + _server.SecurityContexts.Add(serverContext); + serverContext.OscoreEvents += ServerEventHandler; + + for (int i = 0; i < 10; i++) { + serverContext.Sender.IncrementSequenceNumber(); + } + + serverContext.Sender.MaxSequenceNumber = 10; + + CoapClient client = new CoapClient($"coap://localhost:{_serverPort}/abc") + { + OscoreContext = context, + Timeout = 60 * 1000 + }; + + _serverEventChoice = 0; + Response r = client.Get(); + Assert.IsNotNull(r); + Assert.AreEqual("/abc", r.PayloadString); + + client = new CoapClient($"coap://localhost:{_serverPort}/abc") { + OscoreContext = context, + Timeout = 30 * 1000, + }; + client.Observe(); + + Assert.AreEqual(OscoreEvent.EventCode.PivExhaustion, _callbackCode); + } + + [TestMethod] + public void ServerNewSender() + { + SecurityContext context = SecurityContext.DeriveGroupContext(secret, groupId1, clientId, AlgorithmValues.EdDSA, _clientSign1, + new byte[][] {serverId, serverId2}, new OneKey[] {_serverSign1, _serverSign1}); + SecurityContext serverContext = SecurityContext.DeriveGroupContext(secret, groupId1, serverId, AlgorithmValues.EdDSA, _serverSign1, + new byte[][] {clientId2, clientId}, new OneKey[] {_clientSign2, _clientSign1}); + _server.SecurityContexts.Add(serverContext); + serverContext.OscoreEvents += ServerEventHandler; + + for (int i = 0; i < 10; i++) { + serverContext.Sender.IncrementSequenceNumber(); + } + + serverContext.Sender.MaxSequenceNumber = 10; + + CoapClient client = new CoapClient($"coap://localhost:{_serverPort}/abc") { + OscoreContext = context, + Timeout = 60 * 1000 + }; + + _serverEventChoice = 3; + client.Observe(o => { Assert.AreEqual("/abc", o.PayloadString); }); + + Assert.AreEqual(OscoreEvent.EventCode.PivExhaustion, _callbackCode); + } + + [TestMethod] + public void ServerNewSenderGroup() + { + SecurityContext context = SecurityContext.DeriveGroupContext(secret, groupId1, clientId, AlgorithmValues.EdDSA, _clientSign1, + new byte[][] { serverId, serverId2 }, new OneKey[] { _serverSign1, _serverSign1 }); + SecurityContext serverContext = SecurityContext.DeriveGroupContext(secret, groupId1, serverId, AlgorithmValues.EdDSA, _serverSign1, + new byte[][] { clientId2, clientId }, new OneKey[] { _clientSign2, _clientSign1 }); + _server.SecurityContexts.Add(serverContext); + serverContext.OscoreEvents += ServerEventHandler; + + serverContext.Sender.SequenceNumber = 10; + + serverContext.Sender.MaxSequenceNumber = 10; + + CoapClient client = new CoapClient($"coap://localhost:{_serverPort}/abc") + { + OscoreContext = context, + Timeout = 60 * 1000 + }; + + _serverEventChoice = 4; + client.Observe(o => { Assert.AreEqual("/abc", o.PayloadString); }); + + Assert.AreEqual(OscoreEvent.EventCode.PivExhaustion, _callbackCode); + } + + private void CreateServer() + { + CoAPEndPoint endpoint = new CoAPEndPoint(0); + _server = new CoapServer(); + + // _resource = new StorageResource(TARGET, CONTENT_1); + // _server.Add(_resource); + + Resource r2 = new EchoLocation("abc"); + _server.Add(r2); + + r2.Add(new EchoLocation("def")); + + _server.AddEndPoint(endpoint); + _server.Start(); + _serverPort = ((System.Net.IPEndPoint)endpoint.LocalEndPoint).Port; + Console.WriteLine($"Server port = {_serverPort}"); + + SecurityContextSet oscoapContexts = new SecurityContextSet(); + _server.SecurityContexts.Add(SecurityContext.DeriveContext(secret, null, serverId, clientId)); + _server.SecurityContexts.OscoreEvents += ServerEventHandler; + } + + private int _serverEventChoice; + + private void ServerEventHandler(object o, OscoreEvent e) + { + _callbackCode = e.Code; + switch (_serverEventChoice) { + case 0: + break; + + case 1: + e.SecurityContext = SecurityContext.DeriveGroupContext(secret, groupId1, serverId, AlgorithmValues.EdDSA, _serverSign1, + new byte[][]{clientId}, new OneKey[]{_clientSign1}); + break; + + case 2: + e.SecurityContext.AddRecipient(clientId, _clientSign1); + e.RecipientContext = e.SecurityContext.Recipients[clientId]; + break; + + case 3: + e.SecurityContext.ReplaceSender(serverId2, _serverSign1); + break; + + case 4: + e.SecurityContext = SecurityContext.DeriveGroupContext(secret2, groupId2, serverId, AlgorithmValues.EdDSA, _serverSign1, + new byte[][]{clientId}, new OneKey[]{_clientSign1} ); + break; + + default: + Assert.Fail(); + break; + } + } + + class EchoLocation : Resource + { + + public EchoLocation(String name) + : base(name) + { + Observable = true; + RequireSecurity = true; + } + + protected override void DoGet(CoapExchange exchange) + { + String c = this.Uri; + String querys = exchange.Request.UriQuery; + if (querys != "") + { + c += "?" + querys; + } + + exchange.Respond(c); + } + } + } +} diff --git a/CoAP.Test/OSCOAP/Oscoap.cs b/CoAP.Test/OSCOAP/Oscoap.cs index a4f8711..82951fa 100644 --- a/CoAP.Test/OSCOAP/Oscoap.cs +++ b/CoAP.Test/OSCOAP/Oscoap.cs @@ -1,11 +1,7 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using Com.AugustCellars.CoAP.Net; -using Com.AugustCellars.CoAP.OSCOAP; using Com.AugustCellars.CoAP.Server; using Com.AugustCellars.CoAP.Server.Resources; @@ -15,15 +11,11 @@ namespace Com.AugustCellars.CoAP.OSCOAP [TestClass] public class Oscoap { - Int32 _serverPort; + int _serverPort; CoapServer _server; - Resource _resource; - String _expected; - Int32 _notifications; - Boolean _failed; - private static readonly byte[] _ClientId = Encoding.UTF8.GetBytes("client"); - private static readonly byte[] _ServerId = Encoding.UTF8.GetBytes("server"); - private static readonly byte[] _Secret = new byte[] { 01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23 }; + private static readonly byte[] clientId = Encoding.UTF8.GetBytes("client"); + private static readonly byte[] serverId = Encoding.UTF8.GetBytes("server"); + private static readonly byte[] secret = new byte[] { 01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23 }; [TestInitialize] @@ -43,11 +35,10 @@ public void ShutdownServer() public void Ocoap_Get() { CoapClient client = new CoapClient($"coap://localhost:{_serverPort}/abc") { - OscoreContext = SecurityContext.DeriveContext(_Secret, null, _ClientId, _ServerId) + OscoreContext = SecurityContext.DeriveContext(secret, null, clientId, serverId) }; Response r = client.Get(); Assert.AreEqual("/abc", r.PayloadString); - } private void CreateServer() @@ -68,7 +59,7 @@ private void CreateServer() _serverPort = ((System.Net.IPEndPoint) endpoint.LocalEndPoint).Port; SecurityContextSet oscoapContexts = new SecurityContextSet(); - SecurityContextSet.AllContexts.Add(SecurityContext.DeriveContext(_Secret, null, _ServerId, _ClientId)); + _server.SecurityContexts.Add(SecurityContext.DeriveContext(secret, null, serverId, clientId)); } class EchoLocation : Resource