From 1f86350feea975363ff600fdff0d1b623af2a407 Mon Sep 17 00:00:00 2001 From: Jim Schaad Date: Fri, 14 Jul 2017 09:29:20 +0200 Subject: [PATCH 1/5] Comments and expose FileLogManager * Expose the FileLogManager as public - This should have already have been done. Cleanup for HKDF file. --- CoAP.NET/Log/FileLogManager.cs | 9 ++- CoAP.NET/OSCOAP/HKDF.cs | 137 ++++++++++++++++++--------------- 2 files changed, 82 insertions(+), 64 deletions(-) diff --git a/CoAP.NET/Log/FileLogManager.cs b/CoAP.NET/Log/FileLogManager.cs index dce0ba9..4edb266 100644 --- a/CoAP.NET/Log/FileLogManager.cs +++ b/CoAP.NET/Log/FileLogManager.cs @@ -2,8 +2,15 @@ namespace Com.AugustCellars.CoAP.Log { - class FileLogManager : ILogManager + /// + /// Log to file to make life easier to get information + /// + public class FileLogManager : ILogManager { + /// + /// Create a file log manager item + /// + /// File to write things to public FileLogManager(System.IO.TextWriter writeTo) { LogStream = writeTo; diff --git a/CoAP.NET/OSCOAP/HKDF.cs b/CoAP.NET/OSCOAP/HKDF.cs index 28afd94..df9ad7f 100644 --- a/CoAP.NET/OSCOAP/HKDF.cs +++ b/CoAP.NET/OSCOAP/HKDF.cs @@ -7,39 +7,40 @@ using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto; +#pragma warning disable 1591 namespace Com.AugustCellars.CoAP.OSCOAP { #if INCLUDE_OSCOAP - /** - * HMAC-based Extract-and-Expand Key Derivation Function (HKDF) implemented - * according to IETF RFC 5869, May 2010 as specified by H. Krawczyk, IBM - * Research & P. Eronen, Nokia. It uses a HMac internally to compute de OKM - * (output keying material) and is likely to have better security properties - * than KDF's based on just a hash function. - */ + /// + /// HMAC-based Extract-and-Expand Key Derivation Function(HKDF) implemented + /// according to IETF RFC 5869, May 2010 as specified by H.Krawczyk, IBM + /// Research & P.Eronen, Nokia.It uses a HMac internally to compute de OKM + /// (output keying material) and is likely to have better security properties + /// than KDF's based on just a hash function. + /// public class HkdfBytesGenerator : IDerivationFunction { - private HMac hMacHash; - private int hashLen; + private readonly HMac _hMacHash; + private readonly int _hashLen; - private byte[] info; - private byte[] currentT; + private byte[] _info; + private byte[] _currentT; - private int generatedBytes; + private int _generatedBytes; - /** - * Creates a HKDFBytesGenerator based on the given hash function. - * - * @param hash the digest to be used as the source of generatedBytes bytes - */ + /// + /// Creates a HKDFBytesGenerator based on the given hash function. + /// + /// the digest to be used as the source of generatedBytes bytes public HkdfBytesGenerator(IDigest hash) { - this.hMacHash = new HMac(hash); - this.hashLen = hash.GetDigestSize(); + this._hMacHash = new HMac(hash); + this._hashLen = hash.GetDigestSize(); } + /// public virtual void Init(IDerivationParameters parameters) { if (!(parameters is HkdfParameters)) @@ -48,16 +49,16 @@ public virtual void Init(IDerivationParameters parameters) HkdfParameters hkdfParameters = (HkdfParameters)parameters; if (hkdfParameters.SkipExtract) { // use IKM directly as PRK - hMacHash.Init(new KeyParameter(hkdfParameters.GetIkm())); + _hMacHash.Init(new KeyParameter(hkdfParameters.GetIkm())); } else { - hMacHash.Init(Extract(hkdfParameters.GetSalt(), hkdfParameters.GetIkm())); + _hMacHash.Init(Extract(hkdfParameters.GetSalt(), hkdfParameters.GetIkm())); } - info = hkdfParameters.GetInfo(); + _info = hkdfParameters.GetInfo(); - generatedBytes = 0; - currentT = new byte[hashLen]; + _generatedBytes = 0; + _currentT = new byte[_hashLen]; } /** @@ -69,19 +70,19 @@ public virtual void Init(IDerivationParameters parameters) */ private KeyParameter Extract(byte[] salt, byte[] ikm) { - hMacHash.Init(new KeyParameter(ikm)); + _hMacHash.Init(new KeyParameter(ikm)); if (salt == null) { // TODO check if hashLen is indeed same as HMAC size - hMacHash.Init(new KeyParameter(new byte[hashLen])); + _hMacHash.Init(new KeyParameter(new byte[_hashLen])); } else { - hMacHash.Init(new KeyParameter(salt)); + _hMacHash.Init(new KeyParameter(salt)); } - hMacHash.BlockUpdate(ikm, 0, ikm.Length); + _hMacHash.BlockUpdate(ikm, 0, ikm.Length); - byte[] prk = new byte[hashLen]; - hMacHash.DoFinal(prk, 0); + byte[] prk = new byte[_hashLen]; + _hMacHash.DoFinal(prk, 0); return new KeyParameter(prk); } @@ -94,50 +95,60 @@ private KeyParameter Extract(byte[] salt, byte[] ikm) */ private void ExpandNext() { - int n = generatedBytes / hashLen + 1; + int n = _generatedBytes / _hashLen + 1; if (n >= 256) { throw new DataLengthException( "HKDF cannot generate more than 255 blocks of HashLen size"); } // special case for T(0): T(0) is empty, so no update - if (generatedBytes != 0) { - hMacHash.BlockUpdate(currentT, 0, hashLen); + if (_generatedBytes != 0) { + _hMacHash.BlockUpdate(_currentT, 0, _hashLen); } - hMacHash.BlockUpdate(info, 0, info.Length); - hMacHash.Update((byte)n); - hMacHash.DoFinal(currentT, 0); + _hMacHash.BlockUpdate(_info, 0, _info.Length); + _hMacHash.Update((byte)n); + _hMacHash.DoFinal(_currentT, 0); } + /// + /// Get the digest function + /// public virtual IDigest Digest { - get { return hMacHash.GetUnderlyingDigest(); } + get { return _hMacHash.GetUnderlyingDigest(); } } + /// + /// Generate bytes + /// + /// destination + /// diestination offset + /// count of bytes + /// count of bytes public virtual int GenerateBytes(byte[] output, int outOff, int len) { - if (generatedBytes + len > 255 * hashLen) { + if (_generatedBytes + len > 255 * _hashLen) { throw new DataLengthException( "HKDF may only be used for 255 * HashLen bytes of output"); } - if (generatedBytes % hashLen == 0) { + if (_generatedBytes % _hashLen == 0) { ExpandNext(); } // copy what is left in the currentT (1..hash int toGenerate = len; - int posInT = generatedBytes % hashLen; - int leftInT = hashLen - generatedBytes % hashLen; + int posInT = _generatedBytes % _hashLen; + int leftInT = _hashLen - _generatedBytes % _hashLen; int toCopy = System.Math.Min(leftInT, toGenerate); - Array.Copy(currentT, posInT, output, outOff, toCopy); - generatedBytes += toCopy; + Array.Copy(_currentT, posInT, output, outOff, toCopy); + _generatedBytes += toCopy; toGenerate -= toCopy; outOff += toCopy; while (toGenerate > 0) { ExpandNext(); - toCopy = System.Math.Min(hashLen, toGenerate); - Array.Copy(currentT, 0, output, outOff, toCopy); - generatedBytes += toCopy; + toCopy = System.Math.Min(_hashLen, toGenerate); + Array.Copy(_currentT, 0, output, outOff, toCopy); + _generatedBytes += toCopy; toGenerate -= toCopy; outOff += toCopy; } @@ -146,37 +157,37 @@ public virtual int GenerateBytes(byte[] output, int outOff, int len) } } - /** - * Parameter class for the HkdfBytesGenerator class. - */ + /// + /// Parameter class for the HkdfBytesGenerator class. + /// public class HkdfParameters : IDerivationParameters { - private readonly byte[] ikm; - private readonly bool skipExpand; - private readonly byte[] salt; - private readonly byte[] info; + private readonly byte[] _ikm; + private readonly bool _skipExpand; + private readonly byte[] _salt; + private readonly byte[] _info; private HkdfParameters(byte[] ikm, bool skip, byte[] salt, byte[] info) { if (ikm == null) throw new ArgumentNullException("ikm"); - this.ikm = Arrays.Clone(ikm); - this.skipExpand = skip; + this._ikm = Arrays.Clone(ikm); + this._skipExpand = skip; if (salt == null || salt.Length == 0) { - this.salt = null; + this._salt = null; } else { - this.salt = Arrays.Clone(salt); + this._salt = Arrays.Clone(salt); } if (info == null) { - this.info = new byte[0]; + this._info = new byte[0]; } else { - this.info = Arrays.Clone(info); + this._info = Arrays.Clone(info); } } @@ -219,7 +230,7 @@ public static HkdfParameters DefaultParameters(byte[] ikm) */ public virtual byte[] GetIkm() { - return Arrays.Clone(ikm); + return Arrays.Clone(_ikm); } /** @@ -228,7 +239,7 @@ public virtual byte[] GetIkm() * @return true for skipping, false for no skipping of step 1 */ public virtual bool SkipExtract { - get { return skipExpand; } + get { return _skipExpand; } } /** @@ -239,7 +250,7 @@ public virtual bool SkipExtract { */ public virtual byte[] GetSalt() { - return Arrays.Clone(salt); + return Arrays.Clone(_salt); } /** @@ -249,7 +260,7 @@ public virtual byte[] GetSalt() */ public virtual byte[] GetInfo() { - return Arrays.Clone(info); + return Arrays.Clone(_info); } } #endif From c789c1a12fd0f259e5d8d5ccd2d20508c0636203 Mon Sep 17 00:00:00 2001 From: Jim Schaad Date: Tue, 18 Jul 2017 10:49:11 +0200 Subject: [PATCH 2/5] First partial round of changes for multicast --- CoAP.NET/OSCOAP/OscoapLayer.cs | 350 +++++++++++++------------- CoAP.NET/OSCOAP/SecurityContext.cs | 286 ++++++++++++++++----- CoAP.NET/OSCOAP/SecurityContextSet.cs | 59 ++++- 3 files changed, 459 insertions(+), 236 deletions(-) diff --git a/CoAP.NET/OSCOAP/OscoapLayer.cs b/CoAP.NET/OSCOAP/OscoapLayer.cs index fcd2d38..2da513a 100644 --- a/CoAP.NET/OSCOAP/OscoapLayer.cs +++ b/CoAP.NET/OSCOAP/OscoapLayer.cs @@ -63,6 +63,7 @@ void ConfigChanged(Object sender, System.ComponentModel.PropertyChangedEventArgs if (String.Equals(e.PropertyName, "OSCOAP_ReplayWindow")) _replayWindow = config.OSCOAP_ReplayWindow; } + /// public override void SendRequest(INextLayer nextLayer, Exchange exchange, Request request) { if ((request.OscoapContext != null) || (exchange.OscoapContext != null)) { @@ -84,10 +85,7 @@ public override void SendRequest(INextLayer nextLayer, Exchange exchange, Reques MoveRequestHeaders(request, encryptedRequest); - if (_Log.IsInfoEnabled) { - _Log.Info("New inner response message"); - _Log.Info(encryptedRequest.ToString()); - } + _Log.Info(m => m("New inner response message\n{0}", encryptedRequest.ToString())); ctx.Sender.IncrementSequenceNumber(); @@ -115,9 +113,7 @@ public override void SendRequest(INextLayer nextLayer, Exchange exchange, Reques } #endif - if (_Log.IsInfoEnabled) { - _Log.Info("AAD = " + BitConverter.ToString(aad.EncodeToBytes())); - } + _Log.Info(m => m("AAD = {0}", BitConverter.ToString(aad.EncodeToBytes()))); enc.SetExternalData(aad.EncodeToBytes()); #if DEBUG @@ -132,6 +128,9 @@ public override void SendRequest(INextLayer nextLayer, Exchange exchange, Reques enc.AddAttribute(HeaderKeys.PartialIV, CBORObject.FromObject(ctx.Sender.PartialIV), /* Attributes.PROTECTED */ Attributes.DO_NOT_SEND); enc.AddAttribute(HeaderKeys.Algorithm, ctx.Sender.Algorithm, Attributes.DO_NOT_SEND); enc.AddAttribute(HeaderKeys.KeyId, CBORObject.FromObject(ctx.Sender.Id), /*Attributes.PROTECTED*/ Attributes.DO_NOT_SEND); + if (ctx.GroupId != null) { + enc.AddAttribute(CBORObject.FromObject("gid"), CBORObject.FromObject(ctx.GroupId), Attributes.DO_NOT_SEND); + } if (_Log.IsInfoEnabled) { _Log.Info("AAD = " + BitConverter.ToString(aad.EncodeToBytes())); @@ -141,12 +140,16 @@ public override void SendRequest(INextLayer nextLayer, Exchange exchange, Reques enc.Encrypt(ctx.Sender.Key); + if (ctx.SignerKey != null) { + CounterSignature sig = new CounterSignature(ctx.SignerKey); + sig.AddAttribute(HeaderKeys.Algorithm, ctx.SignerKey[CoseKeyKeys.Algorithm], Attributes.DO_NOT_SEND); + sig.SetObject(enc); + CBORObject signatureBytes = sig.EncodeToCBORObject(); + enc.AddAttribute(HeaderKeys.CounterSignature, signatureBytes, Attributes.DO_NOT_SEND); + } + byte[] encBody; -#if OSCOAP_COMPRESS encBody = DoCompression(enc); -#else - encBody= enc.EncodeToBytes(); -#endif if (hasPayload) { request.Payload = encBody; @@ -170,180 +173,177 @@ public override void SendRequest(INextLayer nextLayer, Exchange exchange, Reques base.SendRequest(nextLayer, exchange, request); } + /// public override void ReceiveRequest(INextLayer nextLayer, Exchange exchange, Request request) { - if (request.HasOption(OptionType.Oscoap)) { - Response response; - try { - Option op = request.GetFirstOption(OptionType.Oscoap); - request.RemoveOptions(OptionType.Oscoap); + if (!request.HasOption(OptionType.Oscoap)) { + base.ReceiveRequest(nextLayer, exchange, request); + return; + } + Response response; + try { + Option op = request.GetFirstOption(OptionType.Oscoap); + request.RemoveOptions(OptionType.Oscoap); - Encrypt0Message msg; + Encrypt0Message msg; - if (_Log.IsInfoEnabled) { - _Log.Info("Incoming Request: " + Util.Utils.ToString(request)); - } + _Log.Info(m => m("Incoming Request: {0}", Util.Utils.ToString(request))); -#if OSCOAP_COMPRESS - byte[] raw; - if (op.RawValue.Length== 0) { - raw = request.Payload; - } - else { - raw = op.RawValue; - } - msg = Uncompress(raw); - if (msg == null) { - if (request.Type == MessageType.CON) { - response = new Response(StatusCode.BadOption); - response.PayloadString = "Unable to decompress"; - exchange.SendResponse(response); - } - return; // Ignore messages that have no known security context. - } + byte[] raw; + if (op.RawValue.Length == 0) { + raw = request.Payload; + } + else { + raw = op.RawValue; + } -#else - if (op.RawValue.Length == 0) { - msg = (Encrypt0Message)Com.AugustCellars.COSE.Message.DecodeFromBytes(request.Payload, Tags.Encrypt0); - } - else { - msg = (Encrypt0Message)Com.AugustCellars.COSE.Message.DecodeFromBytes(op.RawValue, Tags.Encrypt0); + msg = Uncompress(raw); + if (msg == null) { + // Only bother to reply to CON messages + if (request.Type == MessageType.CON) { + response = new Response(StatusCode.BadOption); + response.PayloadString = "Unable to decompress"; + exchange.SendResponse(response); } -#endif + return; + } - List contexts = new List(); - SecurityContext ctx = null; + List contexts = new List(); + SecurityContext ctx = null; - if (exchange.OscoapContext != null) { - contexts.Add(exchange.OscoapContext); - } - else { - CBORObject kid = msg.FindAttribute(HeaderKeys.KeyId); - contexts = SecurityContextSet.AllContexts.FindByKid(kid.GetByteString()); - if (contexts.Count == 0) { - response = new Response(StatusCode.Unauthorized); - response.PayloadString = "No Context Found - 1"; - exchange.SendResponse(response); - return; // Ignore messages that have no known security context. - } - } + // We may know the context because it is a follow up on a conversation - + // In which case we can just use the same one. + // M00BUG - Multicast problem of recipient ID? - String partialURI = request.URI.AbsoluteUri; // M00BUG? - - // Build AAD - CBORObject aad = CBORObject.NewArray(); - aad.Add(CBORObject.FromObject(1)); // M00BUG - aad.Add(CBORObject.FromObject(request.Code)); - aad.Add(CBORObject.FromObject(new byte[0])); // encoded I options - aad.Add(CBORObject.FromObject(0)); // Place holder for algorithm - aad.Add(CBORObject.FromObject(msg.FindAttribute(HeaderKeys.KeyId))); - - byte[] payload = null; - byte[] partialIV = msg.FindAttribute(HeaderKeys.PartialIV).GetByteString(); - aad.Add(CBORObject.FromObject(partialIV)); - - byte[] seqNoArray = new byte[8]; - Array.Copy(partialIV, 0, seqNoArray, 8 - partialIV.Length, partialIV.Length); - if (BitConverter.IsLittleEndian) Array.Reverse(seqNoArray); - Int64 seqNo = BitConverter.ToInt64(seqNoArray, 0); - - String responseString = "General decrypt failure"; - - foreach (SecurityContext context in contexts) { - if (_replayWindow && context.Recipient.ReplayWindow.HitTest(seqNo)) { - if (_Log.IsInfoEnabled) { - _Log.Info(String.Format("Hit test on {0} failed", seqNo)); - } - responseString = "Hit test - duplicate"; - continue; - } + if (exchange.OscoapContext != null) { + contexts.Add(exchange.OscoapContext); + } + else { + CBORObject kid = msg.FindAttribute(HeaderKeys.KeyId); + contexts = SecurityContextSet.AllContexts.FindByKid(kid.GetByteString()); + if (contexts.Count == 0) { + response = new Response(StatusCode.Unauthorized); + response.PayloadString = "No Context Found - 1"; + exchange.SendResponse(response); + return; // Ignore messages that have no known security context. + } + } - aad[3] = context.Recipient.Algorithm; + String partialURI = request.URI.AbsoluteUri; // M00BUG? - if (_Log.IsInfoEnabled) { - _Log.Info("AAD = " + BitConverter.ToString(aad.EncodeToBytes())); - _Log.Info("IV = " + BitConverter.ToString(context.Recipient.GetIV(partialIV).GetByteString())); - _Log.Info("Key = " + BitConverter.ToString(context.Recipient.Key)); - } - + // Build AAD + CBORObject aad = CBORObject.NewArray(); + aad.Add(CBORObject.FromObject(1)); // M00BUG + aad.Add(CBORObject.FromObject(request.Code)); + aad.Add(CBORObject.FromObject(new byte[0])); // encoded I options + aad.Add(CBORObject.FromObject(0)); // Place holder for algorithm + aad.Add(CBORObject.FromObject(msg.FindAttribute(HeaderKeys.KeyId))); - msg.SetExternalData(aad.EncodeToBytes()); + byte[] payload = null; + byte[] partialIV = msg.FindAttribute(HeaderKeys.PartialIV).GetByteString(); + aad.Add(CBORObject.FromObject(partialIV)); - msg.AddAttribute(HeaderKeys.Algorithm, context.Recipient.Algorithm, Attributes.DO_NOT_SEND); - msg.AddAttribute(HeaderKeys.IV, context.Recipient.GetIV(partialIV), Attributes.DO_NOT_SEND); + byte[] seqNoArray = new byte[8]; + Array.Copy(partialIV, 0, seqNoArray, 8 - partialIV.Length, partialIV.Length); + if (BitConverter.IsLittleEndian) Array.Reverse(seqNoArray); + Int64 seqNo = BitConverter.ToInt64(seqNoArray, 0); - try { - ctx = context; - payload = msg.Decrypt(context.Recipient.Key); - context.Recipient.ReplayWindow.SetHit(seqNo); - } - catch (Exception e) { - if (_Log.IsInfoEnabled) _Log.Info("--- " + e.ToString()); - responseString = "Decryption Failure"; - ctx = null; - } + String responseString = "General decrypt failure"; - if (ctx != null) { - break; + foreach (SecurityContext context in contexts) { + if (_replayWindow && context.Recipient.ReplayWindow.HitTest(seqNo)) { + if (_Log.IsInfoEnabled) { + _Log.Info(String.Format("Hit test on {0} failed", seqNo)); } + responseString = "Hit test - duplicate"; + continue; } - if (ctx == null) { - if (request.Type == MessageType.CON) { - response = new Response(StatusCode.BadRequest) { - PayloadString = responseString - }; - exchange.SendResponse(response); - } - return; + aad[3] = context.Recipient.Algorithm; + + if (_Log.IsInfoEnabled) { + _Log.Info("AAD = " + BitConverter.ToString(aad.EncodeToBytes())); + _Log.Info("IV = " + BitConverter.ToString(context.Recipient.GetIV(partialIV).GetByteString())); + _Log.Info("Key = " + BitConverter.ToString(context.Recipient.Key)); } - exchange.OscoapContext = ctx; // So we know it on the way back. - request.OscoapContext = ctx; - exchange.OscoapSequenceNumber = partialIV; - byte[] newRequestData = new byte[payload.Length + _FixedHeader.Length]; - Array.Copy(_FixedHeader, newRequestData, _FixedHeader.Length); - Array.Copy(payload, 0, newRequestData, _FixedHeader.Length, payload.Length); + msg.SetExternalData(aad.EncodeToBytes()); - Codec.IMessageDecoder me = Spec.NewMessageDecoder(newRequestData); - Request newRequest = me.DecodeRequest(); + msg.AddAttribute(HeaderKeys.Algorithm, context.Recipient.Algorithm, Attributes.DO_NOT_SEND); + msg.AddAttribute(HeaderKeys.IV, context.Recipient.GetIV(partialIV), Attributes.DO_NOT_SEND); - // Update headers is a pain + try { + ctx = context; + payload = msg.Decrypt(context.Recipient.Key); + context.Recipient.ReplayWindow.SetHit(seqNo); + } + catch (Exception e) { + if (_Log.IsInfoEnabled) _Log.Info("--- " + e.ToString()); + responseString = "Decryption Failure"; + ctx = null; + } - RestoreOptions(request, newRequest); + if (ctx != null) { + break; + } + } - if (_Log.IsInfoEnabled) { - // log.Info(String.Format("Secure message post = " + Util.Utils.ToString(request))); + if (ctx == null) { + if (request.Type == MessageType.CON) { + response = new Response(StatusCode.BadRequest) { + PayloadString = responseString + }; + exchange.SendResponse(response); } + return; + } - // We may want a new exchange at this point if it relates to a new message for blockwise. + exchange.OscoapContext = ctx; // So we know it on the way back. + request.OscoapContext = ctx; + exchange.OscoapSequenceNumber = partialIV; - if (request.HasOption(OptionType.Block2)) { - Exchange.KeyUri keyUri = new Exchange.KeyUri(request.URI, null, request.Source); - BlockHolder block; - _ongoingExchanges.TryGetValue(keyUri, out block); + byte[] newRequestData = new byte[payload.Length + _FixedHeader.Length]; + Array.Copy(_FixedHeader, newRequestData, _FixedHeader.Length); + Array.Copy(payload, 0, newRequestData, _FixedHeader.Length, payload.Length); - if (block != null) { - block.RestoreTo(exchange); - } - } + Codec.IMessageDecoder me = Spec.NewMessageDecoder(newRequestData); + Request newRequest = me.DecodeRequest(); + + // Update headers is a pain + + RestoreOptions(request, newRequest); - request.Payload = newRequest.Payload; + if (_Log.IsInfoEnabled) { + // log.Info(String.Format("Secure message post = " + Util.Utils.ToString(request))); } - catch (Exception e) { - _Log.Error("OSCOAP Layer: reject message because " + e.ToString()); - exchange.OscoapContext = null; - if (request.Type == MessageType.CON) { - response = new Response(StatusCode.Unauthorized); - response.Payload = Encoding.UTF8.GetBytes("Error is " + e.Message); - exchange.SendResponse(response); + // We may want a new exchange at this point if it relates to a new message for blockwise. + + if (request.HasOption(OptionType.Block2)) { + Exchange.KeyUri keyUri = new Exchange.KeyUri(request.URI, null, request.Source); + BlockHolder block; + _ongoingExchanges.TryGetValue(keyUri, out block); + + if (block != null) { + block.RestoreTo(exchange); } - // Ignore messages that we cannot decrypt. - return; } + + request.Payload = newRequest.Payload; + } + catch (Exception e) { + _Log.Error("OSCOAP Layer: reject message because " + e.ToString()); + exchange.OscoapContext = null; + + if (request.Type == MessageType.CON) { + response = new Response(StatusCode.Unauthorized); + response.Payload = Encoding.UTF8.GetBytes("Error is " + e.Message); + exchange.SendResponse(response); + } + // Ignore messages that we cannot decrypt. + return; } base.ReceiveRequest(nextLayer, exchange, request); @@ -483,33 +483,19 @@ public override void ReceiveResponse(INextLayer nextLayer, Exchange exchange, Re } else ctx = exchange.OscoapContext; -#if OSCOAP_COMPRESS byte[] raw; if (op.RawValue.Length > 0) raw = op.RawValue; else raw = response.Payload; - bool fHasObserve = response.HasOption(OptionType.Observe); + bool fServerIv = true; - if (fHasObserve) { msg = Uncompress(raw); if (msg == null) return; - } - else { - msg = Uncompress(raw); - if (msg == null) - return; + if (msg.FindAttribute(HeaderKeys.PartialIV) == null) { msg.AddAttribute(HeaderKeys.PartialIV, CBORObject.FromObject(ctx.Sender.PartialIV), Attributes.DO_NOT_SEND); + fServerIv = false; } -#else - if (op.RawValue.Length > 0) { - msg = (Encrypt0Message)Com.AugustCellars.COSE.Message.DecodeFromBytes(op.RawValue, Tags.Encrypt0); - } - else { - msg = (Encrypt0Message)Com.AugustCellars.COSE.Message.DecodeFromBytes(response.Payload, Tags.Encrypt0); - } -#endif - byte[] partialIV = msg.FindAttribute(HeaderKeys.PartialIV).GetByteString(); @@ -518,12 +504,12 @@ public override void ReceiveResponse(INextLayer nextLayer, Exchange exchange, Re if (BitConverter.IsLittleEndian) Array.Reverse(seqNoArray); Int64 seqNo = BitConverter.ToInt64(seqNoArray, 0); - if (fHasObserve) if (_replayWindow && ctx.Recipient.ReplayWindow.HitTest(seqNo)) return; + if (fServerIv) if (_replayWindow && ctx.Recipient.ReplayWindow.HitTest(seqNo)) return; msg.AddAttribute(HeaderKeys.Algorithm, ctx.Recipient.Algorithm, Attributes.DO_NOT_SEND); CBORObject fullIV = ctx.Recipient.GetIV(partialIV); - if (!fHasObserve) fullIV.GetByteString()[0] += 0x80; + if (!fServerIv) fullIV.GetByteString()[0] += 0x80; msg.AddAttribute(HeaderKeys.IV, fullIV, Attributes.DO_NOT_SEND); // build aad @@ -733,6 +719,20 @@ private static byte[] DoCompression(Encrypt0Message msg) head |= 0x08; } + CBORObject gid = msg.FindAttribute(CBORObject.FromObject("gid")); + if (gid != null) { + cbSize += (1 + gid.GetByteString().Length); + head |= 0x10; + } + + CBORObject sig = msg.FindAttribute(HeaderKeys.CounterSignature); + byte[] sigBytes = null; + if (sig != null) { + sigBytes = sig.EncodeToBytes(); + cbSize += (1 + sigBytes.Length); + head |= 0x20; + } + // Additional items to flag encBody = new byte[cbSize + body.Length]; @@ -750,6 +750,20 @@ private static byte[] DoCompression(Encrypt0Message msg) cbSize += kid.GetByteString().Length + 1; } + if (gid != null) { + if (gid.GetByteString().Length > 255) throw new Exception("GID too large"); + encBody[cbSize] = (byte) gid.GetByteString().Length; + Array.Copy(gid.GetByteString(), 0, encBody, cbSize+1, gid.GetByteString().Length); + cbSize += gid.GetByteString().Length + 1; + } + + if (sig != null) { + if (sigBytes.Length > 255) throw new Exception("GID too large"); + encBody[cbSize] = (byte)sigBytes.Length; + Array.Copy(sigBytes, 0, encBody, cbSize + 1, sig.GetByteString().Length); + cbSize += sigBytes.Length + 1; + } + Array.Copy(body, 0, encBody, cbSize, body.Length); #if DEBUG diff --git a/CoAP.NET/OSCOAP/SecurityContext.cs b/CoAP.NET/OSCOAP/SecurityContext.cs index 4a596c4..ec41ba1 100644 --- a/CoAP.NET/OSCOAP/SecurityContext.cs +++ b/CoAP.NET/OSCOAP/SecurityContext.cs @@ -1,6 +1,6 @@ using System; using System.Collections; - +using System.Collections.Generic; using PeterO.Cbor; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Digests; @@ -9,19 +9,37 @@ namespace Com.AugustCellars.CoAP.OSCOAP { #if INCLUDE_OSCOAP + /// + /// Security context information for use with the OSCOAP structures. + /// This structure supports doing both unicast and multicast transmission and + /// receiption of messages. + /// public class SecurityContext { - public class replayWindow + /// + /// Class implementation used for doing checking if a message is being replayed at us. + /// + public class ReplayWindow { BitArray _hits; Int64 _baseValue; - public replayWindow(int baseValue, int arraySize) + /// + /// create a replaywindow and initialize where the floating window is. + /// + /// Start value to check for hits + /// Size of the replay window + public ReplayWindow(int baseValue, int arraySize) { _baseValue = baseValue; _hits = new BitArray(arraySize); } + /// + /// Check if the value is in the replay window and if it has been set. + /// + /// value to check + /// true if should treat as replay public bool HitTest(Int64 index) { index -= _baseValue; @@ -30,6 +48,10 @@ public bool HitTest(Int64 index) return _hits.Get((int)index); } + /// + /// Set a value has having been seen. + /// + /// value that was seen public void SetHit(Int64 index) { index -= _baseValue; @@ -53,41 +75,64 @@ public void SetHit(Int64 index) } } + /// + /// Crypto information dealing with a single entity that sends data + /// public class EntityContext { - CBORObject _algorithm; - byte[] _baseIV; - byte[] _key; - byte[] _id; - int _sequenceNumber; - replayWindow _replay; - + /// + /// Create new entity cyrpto context structure + /// public EntityContext() { } + /// + /// Create new entity cyrpto context structure + /// Copy constructor - needed to clone key material + /// + /// old structure public EntityContext(EntityContext old) { - _algorithm = old._algorithm; - _baseIV = (byte[])old._baseIV.Clone(); - _key = (byte[])old._key.Clone(); - _id = (byte[])old._id.Clone(); - _replay = new replayWindow(0, 256); - _sequenceNumber = old._sequenceNumber; + Algorithm = old.Algorithm; + BaseIV = (byte[])old.BaseIV.Clone(); + Key = (byte[])old.Key.Clone(); + Id = (byte[])old.Id.Clone(); + ReplayWindow = new ReplayWindow(0, 256); + SequenceNumber = old.SequenceNumber; } - public CBORObject Algorithm - { - get { return _algorithm; } - set { _algorithm = value; } - } - public byte[] BaseIV { get { return _baseIV; } set { _baseIV = value; } } - public byte[] Id { get { return _id; } set { _id = value; } } - public byte[] Key { get { return _key; } set { _key = value; } } - public int SequenceNumber { get { return _sequenceNumber; } set { _sequenceNumber = value; } } + /// + /// What encrption algorithm is being used? + /// + public CBORObject Algorithm { get; set; } + + /// + /// What is the base IV value for this context? + /// + public byte[] BaseIV { get; set; } + + /// + /// What is the identity of this context - matches a key identifier. + /// + public byte[] Id { get; set; } + + /// + /// What is the cryptographic key? + /// + public byte[] Key { get; set; } + + /// + /// What is the current sequence number (IV) for the context? + /// + public int SequenceNumber { get; set; } + + /// + /// Return the sequence number as a byte array. + /// public byte[] PartialIV { get { - byte[] part = BitConverter.GetBytes(_sequenceNumber); + byte[] part = BitConverter.GetBytes(SequenceNumber); if (BitConverter.IsLittleEndian) Array.Reverse(part); int i; for (i = 0; i < part.Length - 1; i++) if (part[i] != 0) break; @@ -98,92 +143,213 @@ public byte[] PartialIV } } + /// + /// Given a partial IV, create the actual IV to use + /// + /// partial IV + /// full IV public CBORObject GetIV(CBORObject partialIV) { return GetIV(partialIV.GetByteString()); } + + /// + /// Given a partial IV, create the actual IV to use + /// + /// partial IV + /// full IV public CBORObject GetIV(byte[] partialIV) { - byte[] IV = (byte[])_baseIV.Clone(); - int offset = IV.Length - partialIV.Length; + byte[] iv = (byte[])BaseIV.Clone(); + int offset = iv.Length - partialIV.Length; - for (int i = 0; i < partialIV.Length; i++) IV[i + offset] ^= partialIV[i]; + for (int i = 0; i < partialIV.Length; i++) iv[i + offset] ^= partialIV[i]; - return CBORObject.FromObject(IV); + return CBORObject.FromObject(iv); } - public replayWindow ReplayWindow { get { return _replay; } set { _replay = value; } } - public void IncrementSequenceNumber() { _sequenceNumber += 1; } + + /// + /// Get/Set the replay window checker for the context. + /// + public ReplayWindow ReplayWindow { get; set; } + + /// + /// Increment the sequence/parital IV + /// + public void IncrementSequenceNumber() { SequenceNumber += 1; } + + /// + /// The key to use for counter signing purposes + /// + public OneKey SigningKey { get; set; } } - static int ContextNumber = 0; - int _contextNo; - public int ContextNo { get { return _contextNo; } } + static int _ContextNumber; + + /// + /// What is the global unique context number for this context. + /// + public int ContextNo { get; private set; } + + /// + /// Return the sender information object + /// + public EntityContext Sender { get; } = new EntityContext(); - EntityContext _sender = new EntityContext(); - public EntityContext Sender { get { return _sender; } } + /// + /// Key to produce counter signatures with. + /// + public OneKey SignerKey { get; } - EntityContext _recipient = new EntityContext(); - public EntityContext Recipient { get { return _recipient; } } + /// + /// Return the single receipient object + /// + public EntityContext Recipient { get; private set; } + public Dictionary Recipients { get; private set; } + + /// + /// Group ID for multi-cast. + /// + public byte[] GroupId { get; set; } + + /// + /// Create a new empty security context + /// public SecurityContext() { } + + /// + /// Create a new security context to hold info for group. + /// + /// + public SecurityContext(byte[] groupId) + { + Recipients = new Dictionary(); + GroupId = groupId; + } + + /// + /// Clone a security context - needed because key info needs to be copied. + /// + /// context to clone public SecurityContext(SecurityContext old) { - _contextNo = old._contextNo; - _sender = new EntityContext(old._sender); - _recipient = new EntityContext(old._recipient); + ContextNo = old.ContextNo; + Sender = new EntityContext(old.Sender); + if (old.Recipient != null) Recipient = new EntityContext(old.Recipient); + if (old.Recipients != null) { + Recipients = new Dictionary(); + foreach (var item in old.Recipients) { + Recipients[item.Key] = new EntityContext(new EntityContext(item.Value)); + } + } } - public static SecurityContext DeriveContext(byte[] MasterSecret, byte[] SenderId, byte[] RecipientId, byte[] MasterSalt = null, CBORObject AEADAlg = null, CBORObject KeyAgreeAlg = null) + /// + /// Given the set of inputs, perform the crptographic operations that are needed + /// to build a security context for a single sender and recipient. + /// + /// pre-shared key + /// name assigned to sender + /// name assigned to recipient + /// salt value + /// encryption algorithm + /// key agreement algorithm + /// + public static SecurityContext DeriveContext(byte[] masterSecret, byte[] senderId, byte[] recipientId, byte[] masterSalt = null, CBORObject algAEAD = null, CBORObject algKeyAgree = null) { SecurityContext ctx = new SecurityContext(); - if (AEADAlg == null) ctx.Sender.Algorithm = AlgorithmValues.AES_CCM_64_64_128; - else ctx.Sender.Algorithm = AEADAlg; - - if (SenderId == null) throw new ArgumentNullException("SenderId"); - ctx.Sender.Id = SenderId; + if (algAEAD == null) ctx.Sender.Algorithm = AlgorithmValues.AES_CCM_64_64_128; + else ctx.Sender.Algorithm = algAEAD; + ctx.Sender.Id = senderId ?? throw new ArgumentNullException(nameof(senderId)); + ctx.Recipient = new EntityContext(); ctx.Recipient.Algorithm = ctx.Sender.Algorithm; - if (RecipientId == null) throw new ArgumentNullException("RecipientId"); - ctx.Recipient.Id = RecipientId; + ctx.Recipient.Id = recipientId ?? throw new ArgumentNullException(nameof(recipientId)); - ctx.Recipient.ReplayWindow = new replayWindow(0, 64); + ctx.Recipient.ReplayWindow = new ReplayWindow(0, 64); CBORObject info = CBORObject.NewArray(); - info.Add(SenderId); // 0 + info.Add(senderId); // 0 info.Add(ctx.Sender.Algorithm); // 1 info.Add("Key"); // 2 info.Add(128/8); // 3 in bytes IDigest sha256 = new Sha256Digest(); IDerivationFunction hkdf = new HkdfBytesGenerator(sha256); - hkdf.Init(new HkdfParameters(MasterSecret, MasterSalt, info.EncodeToBytes())); + hkdf.Init(new HkdfParameters(masterSecret, masterSalt, info.EncodeToBytes())); ctx.Sender.Key = new byte[128/8]; hkdf.GenerateBytes(ctx.Sender.Key, 0, ctx.Sender.Key.Length); - info[0] = CBORObject.FromObject(RecipientId); - hkdf.Init(new HkdfParameters(MasterSecret, MasterSalt, info.EncodeToBytes())); + info[0] = CBORObject.FromObject(recipientId); + hkdf.Init(new HkdfParameters(masterSecret, masterSalt, info.EncodeToBytes())); ctx.Recipient.Key = new byte[128/8]; hkdf.GenerateBytes(ctx.Recipient.Key, 0, ctx.Recipient.Key.Length); info[2] = CBORObject.FromObject("IV"); info[3] = CBORObject.FromObject(56/8); - hkdf.Init(new HkdfParameters(MasterSecret, MasterSalt, info.EncodeToBytes())); + hkdf.Init(new HkdfParameters(masterSecret, masterSalt, info.EncodeToBytes())); ctx.Recipient.BaseIV = new byte[56/8]; hkdf.GenerateBytes(ctx.Recipient.BaseIV, 0, ctx.Recipient.BaseIV.Length); - info[0] = CBORObject.FromObject(SenderId); - hkdf.Init(new HkdfParameters(MasterSecret, MasterSalt, info.EncodeToBytes())); + info[0] = CBORObject.FromObject(senderId); + hkdf.Init(new HkdfParameters(masterSecret, masterSalt, info.EncodeToBytes())); ctx.Sender.BaseIV = new byte[56/8]; hkdf.GenerateBytes(ctx.Sender.BaseIV, 0, ctx.Sender.BaseIV.Length); // Give a unique context number for doing comparisons - ctx._contextNo = SecurityContext.ContextNumber; - SecurityContext.ContextNumber += 1; + ctx.ContextNo = _ContextNumber; + _ContextNumber += 1; + + return ctx; + } + + /// + /// Given the set of inputs, perform the crptographic operations that are needed + /// to build a security context for a single sender and recipient. + /// + /// pre-shared key + /// name assigned to sender + /// salt value + /// encryption algorithm + /// key agreement algorithm + /// + public static EntityContext DeriveEntityContext(byte[] masterSecret, byte[] entityId, byte[] masterSalt = null, CBORObject algAEAD = null, CBORObject algKeyAgree = null) + { + EntityContext ctx = new EntityContext(); + + if (algAEAD == null) ctx.Algorithm = AlgorithmValues.AES_CCM_64_64_128; + else ctx.Algorithm = algAEAD; + ctx.Id = entityId ?? throw new ArgumentNullException(nameof(entityId)); + + ctx.ReplayWindow = new ReplayWindow(0, 64); + + CBORObject info = CBORObject.NewArray(); + + // M00TODO - add the group id into this + + info.Add(entityId); // 0 + info.Add(ctx.Algorithm); // 1 + info.Add("Key"); // 2 + info.Add(128 / 8); // 3 in bytes + + IDigest sha256 = new Sha256Digest(); + IDerivationFunction hkdf = new HkdfBytesGenerator(sha256); + hkdf.Init(new HkdfParameters(masterSecret, masterSalt, info.EncodeToBytes())); + + ctx.Key = new byte[128 / 8]; + hkdf.GenerateBytes(ctx.Key, 0, ctx.Key.Length); + + info[2] = CBORObject.FromObject("IV"); + info[3] = CBORObject.FromObject(56 / 8); + hkdf.Init(new HkdfParameters(masterSecret, masterSalt, info.EncodeToBytes())); + ctx.BaseIV = new byte[56 / 8]; + hkdf.GenerateBytes(ctx.BaseIV, 0, ctx.BaseIV.Length); return ctx; } diff --git a/CoAP.NET/OSCOAP/SecurityContextSet.cs b/CoAP.NET/OSCOAP/SecurityContextSet.cs index 32b4650..4215878 100644 --- a/CoAP.NET/OSCOAP/SecurityContextSet.cs +++ b/CoAP.NET/OSCOAP/SecurityContextSet.cs @@ -1,27 +1,47 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace Com.AugustCellars.CoAP.OSCOAP { + /// + /// Collection of OSCOAP security contexts + /// public class SecurityContextSet { - List _allContexts = new List(); + /// + /// Collection of all OSCOAP security contexts on the system. + /// + public static SecurityContextSet AllContexts = new SecurityContextSet(); - public int Count { get { return _allContexts.Count; } } + /// + /// Get the count of all security contexts. + /// + public int Count { get => All.Count;} + /// + /// Add a new context to the set + /// + /// context to add public void Add(SecurityContext ctx) { - _allContexts.Add(ctx); + All.Add(ctx); } - public List All { get { return _allContexts; } } + /// + /// Secuirty contexts for the object + /// + public List All { get; } = new List(); + /// + /// Find all security contexts that match this key identifier + /// + /// key id to search + /// set of contexts public List FindByKid(byte[] kid) { List contexts = new List(); - foreach (SecurityContext ctx in _allContexts) { - if (kid.Length == ctx.Recipient.Id.Length) { + foreach (SecurityContext ctx in All) { + if (ctx.Recipient != null && kid.Length == ctx.Recipient.Id.Length) { bool match = true; for (int i=0; i FindByKid(byte[] kid) } return contexts; } + + /// + /// Find all security contexts that match this group identifier + /// + /// group id to search + /// set of contexts + public List FindByGroupId(byte[] groupId) + { + List contexts = new List(); + foreach (SecurityContext ctx in All) { + if (ctx.GroupId != null && groupId.Length == ctx.GroupId.Length) { + bool match = true; + for (int i = 0; i < groupId.Length; i++) { + if (groupId[i] != ctx.Recipient.Id[i]) { + match = false; + break; + } + } + if (match) contexts.Add(ctx); + } + } + return contexts; + } } } From 384ed69fbc23ded14304bb77147c52a27e1780f0 Mon Sep 17 00:00:00 2001 From: Jim Schaad Date: Tue, 18 Jul 2017 10:49:26 +0200 Subject: [PATCH 3/5] Book keeping information --- CoAP.Example/CoAP.Server/CoAP.Server.NET45.csproj | 13 +++++++++++-- CoAP.Example/CoAP.Server/packages.config | 5 +++++ CoAP.NET/packages.config | 3 +-- coap.nuspec | 5 ++++- 4 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 CoAP.Example/CoAP.Server/packages.config diff --git a/CoAP.Example/CoAP.Server/CoAP.Server.NET45.csproj b/CoAP.Example/CoAP.Server/CoAP.Server.NET45.csproj index 7db7221..5c77e7b 100644 --- a/CoAP.Example/CoAP.Server/CoAP.Server.NET45.csproj +++ b/CoAP.Example/CoAP.Server/CoAP.Server.NET45.csproj @@ -1,4 +1,4 @@ - + Debug @@ -43,6 +43,12 @@ 4 + + ..\..\packages\PeterO.Cbor.2.5.2\lib\portable-net45+dnxcore50+netcore45+win+wpa81+wp80\CBOR.dll + + + ..\..\packages\PeterO.Numbers.0.4.0\lib\portable-net45+dnxcore50+netcore45+win+wpa81+wp80\Numbers.dll + @@ -64,6 +70,9 @@ CoAP.NET45 + + + - + \ No newline at end of file diff --git a/CoAP.Example/CoAP.Server/packages.config b/CoAP.Example/CoAP.Server/packages.config new file mode 100644 index 0000000..8cd5c5a --- /dev/null +++ b/CoAP.Example/CoAP.Server/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/CoAP.NET/packages.config b/CoAP.NET/packages.config index 0aeabb2..7d21b65 100644 --- a/CoAP.NET/packages.config +++ b/CoAP.NET/packages.config @@ -1,7 +1,6 @@  - - + diff --git a/coap.nuspec b/coap.nuspec index 95b9a21..a10c69e 100644 --- a/coap.nuspec +++ b/coap.nuspec @@ -2,7 +2,7 @@ Com.AugustCellars.CoAP - 1.1.6 + 1.1.7 Com.AugustCellars.CoAP Jim Schaad jimsch @@ -15,6 +15,9 @@ 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 to be used for commercial purposes. It is intented only for research and verification work. +1.1.7 + - Missed the public on FileLogManager class + 1.1.6 - Re-enable DTLS reliability layer. It was removed for debugging purposes. - Re-enable observer reconnect attempts. The ability to disable it remains, this just changes the default value. From 51657dea54709e106e0848db0f5acecd26af73db Mon Sep 17 00:00:00 2001 From: Jim Schaad Date: Tue, 18 Jul 2017 10:50:50 +0200 Subject: [PATCH 4/5] check point --- CoAP.NET/OSCOAP/OscoapLayer.cs | 41 +++++++++++------------------- CoAP.NET/OSCOAP/SecurityContext.cs | 13 ++++++---- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/CoAP.NET/OSCOAP/OscoapLayer.cs b/CoAP.NET/OSCOAP/OscoapLayer.cs index 2da513a..7d4a3e8 100644 --- a/CoAP.NET/OSCOAP/OscoapLayer.cs +++ b/CoAP.NET/OSCOAP/OscoapLayer.cs @@ -140,10 +140,12 @@ public override void SendRequest(INextLayer nextLayer, Exchange exchange, Reques enc.Encrypt(ctx.Sender.Key); - if (ctx.SignerKey != null) { - CounterSignature sig = new CounterSignature(ctx.SignerKey); - sig.AddAttribute(HeaderKeys.Algorithm, ctx.SignerKey[CoseKeyKeys.Algorithm], Attributes.DO_NOT_SEND); + if (ctx.Sender.SigningKey != null) { + CounterSignature sig = new CounterSignature(ctx.Sender.SigningKey); + sig.AddAttribute(HeaderKeys.Algorithm, ctx.Sender.SigningKey[CoseKeyKeys.Algorithm], Attributes.DO_NOT_SEND); sig.SetObject(enc); + aad = ctx.Sender.SigningKey[CoseKeyKeys.Algorithm]; + sig.SetExternalData(aad.EncodeToBytes()); CBORObject signatureBytes = sig.EncodeToCBORObject(); enc.AddAttribute(HeaderKeys.CounterSignature, signatureBytes, Attributes.DO_NOT_SEND); } @@ -220,8 +222,16 @@ public override void ReceiveRequest(INextLayer nextLayer, Exchange exchange, Req contexts.Add(exchange.OscoapContext); } else { + CBORObject gid = msg.FindAttribute(CBORObject.FromObject("gid")); CBORObject kid = msg.FindAttribute(HeaderKeys.KeyId); - contexts = SecurityContextSet.AllContexts.FindByKid(kid.GetByteString()); + + if (gid != null) { + contexts = SecurityContextSet.AllContexts.FindByGroupId(gid.GetByteString()); + } + else { + contexts = SecurityContextSet.AllContexts.FindByKid(kid.GetByteString()); + } + if (contexts.Count == 0) { response = new Response(StatusCode.Unauthorized); response.PayloadString = "No Context Found - 1"; @@ -389,8 +399,7 @@ public override void SendResponse(INextLayer nextLayer, Exchange exchange, Respo enc.SetContent(msg2); enc.SetExternalData(aad.EncodeToBytes()); -#if OSCOAP_COMPRESS - if (response.HasOption(OptionType.Observe)) { + if (response.HasOption(OptionType.Observe) || ctx.Sender.SigningKey != null) { enc.AddAttribute(HeaderKeys.PartialIV, CBORObject.FromObject(ctx.Sender.PartialIV), Attributes.PROTECTED); enc.AddAttribute(HeaderKeys.IV, ctx.Sender.GetIV(ctx.Sender.PartialIV), Attributes.DO_NOT_SEND); ctx.Sender.IncrementSequenceNumber(); @@ -403,32 +412,12 @@ public override void SendResponse(INextLayer nextLayer, Exchange exchange, Respo enc.AddAttribute(HeaderKeys.IV, CBORObject.FromObject(ivX), Attributes.DO_NOT_SEND); } -#else - enc.AddAttribute(HeaderKeys.PartialIV, CBORObject.FromObject(ctx.Sender.PartialIV), Attributes.PROTECTED); - enc.AddAttribute(HeaderKeys.IV, ctx.Sender.GetIV(ctx.Sender.PartialIV), Attributes.DO_NOT_SEND); - ctx.Sender.IncrementSequenceNumber(); -#endif enc.AddAttribute(HeaderKeys.Algorithm, ctx.Sender.Algorithm, Attributes.DO_NOT_SEND); enc.Encrypt(ctx.Sender.Key); byte[] finalBody; -#if OSCOAP_COMPRESS -#if false - - if (response.HasOption(OptionType.Observe)) { - finalBody = DoCompression(enc); - } - else { - CBORObject msgX = enc.EncodeToCBORObject(); - finalBody = msgX[2].GetByteString(); - } -#else finalBody = DoCompression(enc); -#endif -#else - finalBody = enc.EncodeToBytes(); -#endif if (hasPayload) { response.Payload = finalBody; diff --git a/CoAP.NET/OSCOAP/SecurityContext.cs b/CoAP.NET/OSCOAP/SecurityContext.cs index ec41ba1..7e04be5 100644 --- a/CoAP.NET/OSCOAP/SecurityContext.cs +++ b/CoAP.NET/OSCOAP/SecurityContext.cs @@ -98,6 +98,7 @@ public EntityContext(EntityContext old) Id = (byte[])old.Id.Clone(); ReplayWindow = new ReplayWindow(0, 256); SequenceNumber = old.SequenceNumber; + SigningKey = old.SigningKey; } /// @@ -196,16 +197,14 @@ public CBORObject GetIV(byte[] partialIV) /// public EntityContext Sender { get; } = new EntityContext(); - /// - /// Key to produce counter signatures with. - /// - public OneKey SignerKey { get; } - /// /// Return the single receipient object /// public EntityContext Recipient { get; private set; } + /// + /// Get the set of all recipients for group. + /// public Dictionary Recipients { get; private set; } /// @@ -235,6 +234,7 @@ public SecurityContext(byte[] groupId) public SecurityContext(SecurityContext old) { ContextNo = old.ContextNo; + GroupId = old.GroupId; Sender = new EntityContext(old.Sender); if (old.Recipient != null) Recipient = new EntityContext(old.Recipient); if (old.Recipients != null) { @@ -354,6 +354,9 @@ public static EntityContext DeriveEntityContext(byte[] masterSecret, byte[] enti return ctx; } + + + #if DEBUG static int _FutzError = 0; static public int FutzError { From a5b7d52809824cd2606bede177d8d51b221b9117 Mon Sep 17 00:00:00 2001 From: Jim Schaad Date: Fri, 21 Jul 2017 17:07:23 +0200 Subject: [PATCH 5/5] Checkpoint --- CoAP.NET/CoAP.NET45.csproj | 2 +- CoAP.NET/Net/Exchange.cs | 10 ++- CoAP.NET/OptionType.cs | 14 ++++ CoAP.NET/Stack/BlockwiseLayer.cs | 139 +++++++++++++++---------------- 4 files changed, 89 insertions(+), 76 deletions(-) diff --git a/CoAP.NET/CoAP.NET45.csproj b/CoAP.NET/CoAP.NET45.csproj index 3d60997..c525a34 100644 --- a/CoAP.NET/CoAP.NET45.csproj +++ b/CoAP.NET/CoAP.NET45.csproj @@ -29,7 +29,7 @@ true obj\Release\NET45 bin\Release\NET45\ - TRACE;COAPALL;INCLUDE_OSCOAP;OSCOAP_COMPRESS + TRACE;COAPALL;INCLUDE_OSCOAP;FRESHNESS prompt 4 bin\Release\NET45\CoAP.XML diff --git a/CoAP.NET/Net/Exchange.cs b/CoAP.NET/Net/Exchange.cs index c83fff6..1016844 100644 --- a/CoAP.NET/Net/Exchange.cs +++ b/CoAP.NET/Net/Exchange.cs @@ -292,11 +292,17 @@ public T GetOrAdd(Object key, Func valueFactory) return (T)GetOrAdd(key, o => valueFactory(o)); } + /// + /// Set an object in the attribute map based on it's key. + /// If a previous object existed, return it. + /// + /// Key to use to save the object + /// value to save + /// old object if one exists. public Object Set(Object key, Object value) { Object old = null; - _attributes.AddOrUpdate(key, value, (k, v) => - { + _attributes.AddOrUpdate(key, value, (k, v) => { old = v; return value; }); diff --git a/CoAP.NET/OptionType.cs b/CoAP.NET/OptionType.cs index c211f2b..68d3439 100644 --- a/CoAP.NET/OptionType.cs +++ b/CoAP.NET/OptionType.cs @@ -148,6 +148,20 @@ public enum OptionType Oscoap = 65025, #endif +#if FRESHNESS + /// + /// Resend request for freshness purposes + /// draft-amsuess-core-repeat-request-tag + /// + ResendRequest = 65026, + + /// + /// Request defined ETAG + /// draft-amsuess-core-repeat-request-tag + /// + Request_ETag = 65027, +#endif + /// /// no-op for fenceposting /// draft-bormann-coap-misc-04 diff --git a/CoAP.NET/Stack/BlockwiseLayer.cs b/CoAP.NET/Stack/BlockwiseLayer.cs index 90b7aaf..3c62a6d 100644 --- a/CoAP.NET/Stack/BlockwiseLayer.cs +++ b/CoAP.NET/Stack/BlockwiseLayer.cs @@ -52,15 +52,15 @@ void ConfigChanged(Object sender, System.ComponentModel.PropertyChangedEventArgs /// public override void SendRequest(INextLayer nextLayer, Exchange exchange, Request request) { - if (request.HasOption(OptionType.Block2) && request.Block2.NUM > 0) - { + if (request.HasOption(OptionType.Block2) && request.Block2.NUM > 0) { // This is the case if the user has explicitly added a block option // for random access. // Note: We do not regard it as random access when the block num is // 0. This is because the user might just want to do early block // size negotiation but actually wants to receive all blocks. - if (log.IsDebugEnabled) - log.Debug("Request carries explicit defined block2 option: create random access blockwise status"); + + log.Debug("Request carries explicit defined block2 option: create random access blockwise status"); + BlockwiseStatus status = new BlockwiseStatus(request.ContentFormat); BlockOption block2 = request.Block2; status.CurrentSZX = block2.SZX; @@ -69,19 +69,17 @@ public override void SendRequest(INextLayer nextLayer, Exchange exchange, Reques exchange.ResponseBlockStatus = status; base.SendRequest(nextLayer, exchange, request); } - else if (RequiresBlockwise(request)) - { + else if (RequiresBlockwise(request)) { // This must be a large POST or PUT request - if (log.IsDebugEnabled) - log.Debug("Request payload " + request.PayloadSize + "/" + _maxMessageSize + " requires Blockwise."); + log.Debug(m => m("Request payload {0}/{1} requires Blockwise.", request.PayloadSize, _maxMessageSize)); + BlockwiseStatus status = FindRequestBlockStatus(exchange, request); Request block = GetNextRequestBlock(request, status); exchange.RequestBlockStatus = status; exchange.CurrentRequest = block; base.SendRequest(nextLayer, exchange, block); } - else - { + else { exchange.CurrentRequest = request; base.SendRequest(nextLayer, exchange, request); } @@ -215,44 +213,44 @@ public override void ReceiveRequest(INextLayer nextLayer, Exchange exchange, Req public override void SendResponse(INextLayer nextLayer, Exchange exchange, Response response) { BlockOption block1 = exchange.Block1ToAck; - if (block1 != null) + if (block1 != null) { exchange.Block1ToAck = null; + } - if (RequiresBlockwise(exchange, response)) - { - if (log.IsDebugEnabled) - log.Debug("Response payload " + response.PayloadSize + "/" + _maxMessageSize + " requires Blockwise"); + if (RequiresBlockwise(exchange, response)) { + log.Debug(m => m("Response payload {0}/{1} requires Blockwise", response.PayloadSize, _maxMessageSize)); BlockwiseStatus status = FindResponseBlockStatus(exchange, response); Response block = GetNextResponseBlock(response, status); - - if (block1 != null) // in case we still have to ack the last block1 + + if (block1 != null) { + // in case we still have to ack the last block1 block.SetOption(block1); - if (block.Token == null) + } + + if (block.Token == null) { block.Token = exchange.Request.Token; + } - if (status.Complete) - { + if (status.Complete) { // clean up blockwise status - if (log.IsDebugEnabled) - log.Debug("Ongoing finished on first block " + status); + log.Debug(m => m("Ongoing finished on first block {0}", status)); exchange.ResponseBlockStatus = null; ClearBlockCleanup(exchange); } - else - { - if (log.IsDebugEnabled) - log.Debug("Ongoing started " + status); + else { + log.Debug(m => m("Ongoing started {0}", status)); } exchange.CurrentResponse = block; base.SendResponse(nextLayer, exchange, block); } - else - { - if (block1 != null) + else { + if (block1 != null) { response.SetOption(block1); + } + exchange.CurrentResponse = response; // Block1 transfer completed ClearBlockCleanup(exchange); @@ -465,21 +463,22 @@ private BlockwiseStatus FindRequestBlockStatus(Exchange exchange, Request reques private BlockwiseStatus FindResponseBlockStatus(Exchange exchange, Response response) { BlockwiseStatus status = exchange.ResponseBlockStatus; - if (status == null) - { - status = new BlockwiseStatus(response.ContentType); - status.CurrentSZX = BlockOption.EncodeSZX(_defaultBlockSize); + + if (status == null) { + status = new BlockwiseStatus(response.ContentType) { + CurrentSZX = BlockOption.EncodeSZX(_defaultBlockSize) + }; exchange.ResponseBlockStatus = status; - if (log.IsDebugEnabled) - log.Debug("There is no blockwise status yet. Create and set new Block2 status: " + status); + + log.Debug(m=> m("There is no blockwise status yet. Create and set new Block2 status: {0}", status)); } - else - { - if (log.IsDebugEnabled) - log.Debug("Current Block2 status: " + status); + else { + log.Debug(m => m("Current Block2 status: {0}", status)); } + // sets a timeout to complete exchange PrepareBlockCleanup(exchange); + return status; } @@ -514,16 +513,15 @@ private Response GetNextResponseBlock(Response response, BlockwiseStatus status) Int32 szx = status.CurrentSZX; Int32 num = status.CurrentNUM; - if (response.HasOption(OptionType.Observe)) - { + if (response.HasOption(OptionType.Observe)) { // a blockwise notification transmits the first block only block = response; } - else - { - block = new Response(response.StatusCode); - block.Destination = response.Destination; - block.Token = response.Token; + else { + block = new Response(response.StatusCode) { + Destination = response.Destination, + Token = response.Token + }; block.SetOptions(response.GetOptions()); block.TimedOut += (o, e) => response.IsTimedOut = true; } @@ -531,8 +529,8 @@ private Response GetNextResponseBlock(Response response, BlockwiseStatus status) Int32 payloadSize = response.PayloadSize; Int32 currentSize = 1 << (4 + szx); Int32 from = num * currentSize; - if (payloadSize > 0 && payloadSize > from) - { + + if (payloadSize > 0 && payloadSize > from) { Int32 to = Math.Min((num + 1) * currentSize, response.PayloadSize); Int32 length = to - from; Byte[] blockPayload = new Byte[length]; @@ -548,8 +546,7 @@ private Response GetNextResponseBlock(Response response, BlockwiseStatus status) status.Complete = !m; } - else - { + else { block.AddOption(new BlockOption(OptionType.Block2, num, szx, false)); block.Last = true; status.Complete = true; @@ -598,25 +595,24 @@ private Boolean RequiresBlockwise(Exchange exchange, Response response) /// /// Schedules a clean-up task. + /// If a clean-up task already exists, it will be disposed of. /// Use the to set the timeout. /// protected void PrepareBlockCleanup(Exchange exchange) { - Timer timer = new Timer(); - timer.AutoReset = false; - timer.Interval = _blockTimeout; + Timer timer = new Timer { + AutoReset = false, + Interval = _blockTimeout + }; timer.Elapsed += (o, e) => BlockwiseTimeout(exchange); Timer old = exchange.Set("BlockCleanupTimer", timer) as Timer; - if (old != null) - { - try - { + if (old != null) { + try { old.Stop(); old.Dispose(); } - catch (ObjectDisposedException) - { + catch (ObjectDisposedException) { // ignore } } @@ -630,31 +626,28 @@ protected void PrepareBlockCleanup(Exchange exchange) protected void ClearBlockCleanup(Exchange exchange) { Timer timer = exchange.Remove("BlockCleanupTimer") as Timer; - if (timer != null) - { - try - { + if (timer != null) { + try { timer.Stop(); timer.Dispose(); } - catch (ObjectDisposedException) - { + catch (ObjectDisposedException) { // ignore } } } + /// + /// Time to try and do clean up. + /// + /// exchange to clean up private void BlockwiseTimeout(Exchange exchange) { - if (exchange.Request == null) - { - if (log.IsInfoEnabled) - log.Info("Block1 transfer timed out: " + exchange.CurrentRequest); + if (exchange.Request == null) { + log.Info(m => m("Block1 transfer timed out: {0}", exchange.CurrentRequest)); } - else - { - if (log.IsInfoEnabled) - log.Info("Block2 transfer timed out: " + exchange.Request); + else { + log.Info(m => m("Block2 transfer timed out: {0}", exchange.Request)); } exchange.Complete = true; }