From ddea0e256d697c630126ef6f8f8d3dab6f1cb9a5 Mon Sep 17 00:00:00 2001 From: Jim Schaad Date: Sat, 16 Jun 2018 10:51:59 -0700 Subject: [PATCH] Put in RPK and callbacks for supporting certificates (#48) Setup TLS to have callbacks so that one can look at things like ACE tokens being passed as the PSK identifier. --- CoAP.NET/CoAP.NET45.csproj | 5 +- CoAP.NET/DTLS/DTLSChannel.cs | 14 ++ CoAP.NET/DTLS/DTLSClient.cs | 328 ++++++++++++++++++++++++---- CoAP.NET/DTLS/DTLSClientChannel.cs | 12 + CoAP.NET/DTLS/DTLSClientEndPoint.cs | 11 + CoAP.NET/DTLS/DTLSEndPoint.cs | 12 + CoAP.NET/DTLS/DTLSSession.cs | 32 ++- CoAP.NET/DTLS/DtlsServer.cs | 284 +++++++++++++++++++++--- CoAP.NET/DTLS/TlsEvent.cs | 84 +++++++ CoAP.NET/Server/CoapServer.cs | 31 ++- CoAP.Test/DTLS/DtlsEvents.cs | 142 ++++++++++++ 11 files changed, 872 insertions(+), 83 deletions(-) create mode 100644 CoAP.NET/DTLS/TlsEvent.cs create mode 100644 CoAP.Test/DTLS/DtlsEvents.cs diff --git a/CoAP.NET/CoAP.NET45.csproj b/CoAP.NET/CoAP.NET45.csproj index 4da146a..567581e 100644 --- a/CoAP.NET/CoAP.NET45.csproj +++ b/CoAP.NET/CoAP.NET45.csproj @@ -1,4 +1,4 @@ - + Debug @@ -109,6 +109,7 @@ + @@ -205,4 +206,4 @@ --> - \ No newline at end of file + diff --git a/CoAP.NET/DTLS/DTLSChannel.cs b/CoAP.NET/DTLS/DTLSChannel.cs index ae531d6..f46e94e 100644 --- a/CoAP.NET/DTLS/DTLSChannel.cs +++ b/CoAP.NET/DTLS/DTLSChannel.cs @@ -27,6 +27,7 @@ public class DTLSChannel : IChannel private UDPChannel _udpChannel; private KeySet _serverKeys; private KeySet _userKeys; + public DTLSChannel(KeySet serverKeys, KeySet userKeys) : this(serverKeys, userKeys, 0) { @@ -86,6 +87,8 @@ public Int32 ReceivePacketSize { private Int32 _running; + public EventHandler TlsEventHandler; + public void Start() { if (System.Threading.Interlocked.CompareExchange(ref _running, 1, 0) > 0) { @@ -164,6 +167,7 @@ public ISession GetSession(System.Net.EndPoint ep) session = new DTLSSession(ipEndPoint, DataReceived, _serverKeys, _userKeys); AddSession(session); + session.TlsEventHandler += MyTlsEventHandler; session.Connect(_udpChannel); @@ -175,6 +179,14 @@ public ISession GetSession(System.Net.EndPoint ep) return session; } + private void MyTlsEventHandler(Object o, TlsEvent e) + { + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(o, e); + } + } + public void Send(byte[] data, ISession sessionReceive, System.Net.EndPoint ep) { try { @@ -184,6 +196,7 @@ public void Send(byte[] data, ISession sessionReceive, System.Net.EndPoint ep) if (session == null) { session = new DTLSSession(ipEP, DataReceived, _serverKeys, _userKeys); + session.TlsEventHandler += MyTlsEventHandler; AddSession(session); session.Connect(_udpChannel); } @@ -211,6 +224,7 @@ private void ReceiveData(Object sender, DataReceivedEventArgs e) } DTLSSession sessionNew = new DTLSSession((IPEndPoint) e.EndPoint, DataReceived, _serverKeys, _userKeys); + sessionNew.TlsEventHandler = MyTlsEventHandler; _sessionList.Add(sessionNew); new Thread(() => Accept(sessionNew, e.Data)).Start(); } diff --git a/CoAP.NET/DTLS/DTLSClient.cs b/CoAP.NET/DTLS/DTLSClient.cs index 28fb3f1..8fd2ea2 100644 --- a/CoAP.NET/DTLS/DTLSClient.cs +++ b/CoAP.NET/DTLS/DTLSClient.cs @@ -2,11 +2,20 @@ using System.Collections; using System.Threading.Tasks; - +using Com.AugustCellars.COSE; +using Org.BouncyCastle.Asn1.Nist; using Org.BouncyCastle.Crypto.Tls; using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.X509; +using PeterO.Cbor; namespace Com.AugustCellars.CoAP.DTLS @@ -16,6 +25,8 @@ class DtlsClient : DefaultTlsClient { private TlsPskIdentity _mPskIdentity; private TlsSession _mSession; + public EventHandler TlsEventHandler; + public OneKey _rawPublicKey; internal DtlsClient(TlsSession session, TlsPskIdentity pskIdentity) { @@ -23,30 +34,58 @@ internal DtlsClient(TlsSession session, TlsPskIdentity pskIdentity) _mPskIdentity = pskIdentity; } - public override ProtocolVersion MinimumVersion { - get { return ProtocolVersion.DTLSv10; } + internal DtlsClient(TlsSession session, OneKey userKey) + { + _mSession = session; + _rawPublicKey = userKey; } - public override ProtocolVersion ClientVersion + private void OnTlsEvent(Object o, TlsEvent e) { - get { return ProtocolVersion.DTLSv12; } + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(o, e); + } } + public override ProtocolVersion MinimumVersion => ProtocolVersion.DTLSv10; + + public override ProtocolVersion ClientVersion => ProtocolVersion.DTLSv12; + public override int[] GetCipherSuites() { - return new int[] { CipherSuite.TLS_PSK_WITH_AES_128_CCM_8}; -#if false - return Arrays.Concatenate(base.GetCipherSuites(), - new int[] { + int[] i; + + if (_rawPublicKey != null) { + i = new int[] { + CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, + CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 + }; + } + else { + i = new int[] { CipherSuite.TLS_PSK_WITH_AES_128_CCM_8, - }); -#endif + }; + } + + TlsEvent e = new TlsEvent(TlsEvent.EventCode.GetCipherSuites) { + IntValues = i + }; + + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(this, e); + } + + return e.IntValues; } public override IDictionary GetClientExtensions() { IDictionary clientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(base.GetClientExtensions()); + + // TlsExtensionsUtilities.AddEncryptThenMacExtension(clientExtensions); // TlsExtensionsUtilities.AddExtendedMasterSecretExtension(clientExtensions); { @@ -56,12 +95,29 @@ public override IDictionary GetClientExtensions() // TlsExtensionsUtilities.AddMaxFragmentLengthExtension(clientExtensions, MaxFragmentLength.pow2_9); // TlsExtensionsUtilities.AddPaddingExtension(clientExtensions, mContext.SecureRandom.Next(16)); // TlsExtensionsUtilities.AddTruncatedHMacExtension(clientExtensions); + + if (_rawPublicKey != null) { + TlsExtensionsUtilities.AddClientCertificateTypeExtensionClient(clientExtensions, new byte[]{2}); + TlsExtensionsUtilities.AddServerCertificateTypeExtensionClient(clientExtensions, new byte[]{2}); + } } - return clientExtensions; + + TlsEvent e = new TlsEvent(TlsEvent.EventCode.GetExtensions) { + Dictionary = clientExtensions + }; + + + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(this, e); + } + + return e.Dictionary; } + public override TlsAuthentication GetAuthentication() { - return new MyTlsAuthentication(mContext); + return new MyTlsAuthentication(mContext, _rawPublicKey); } public override TlsKeyExchange GetKeyExchange() @@ -69,25 +125,74 @@ public override TlsKeyExchange GetKeyExchange() int keyExchangeAlgorithm = TlsUtilities.GetKeyExchangeAlgorithm(mSelectedCipherSuite); switch (keyExchangeAlgorithm) { - case KeyExchangeAlgorithm.DHE_PSK: - case KeyExchangeAlgorithm.ECDHE_PSK: - case KeyExchangeAlgorithm.PSK: - case KeyExchangeAlgorithm.RSA_PSK: - return CreatePskKeyExchange(keyExchangeAlgorithm); - - case KeyExchangeAlgorithm.ECDH_anon: - case KeyExchangeAlgorithm.ECDH_ECDSA: - case KeyExchangeAlgorithm.ECDH_RSA: - return CreateECDHKeyExchange(keyExchangeAlgorithm); - - default: - /* - * Note: internal error here; the TlsProtocol implementation verifies that the - * server-selected cipher suite was in the list of client-offered cipher suites, so if - * we now can't produce an implementation, we shouldn't have offered it! - */ - throw new TlsFatalAlert(AlertDescription.internal_error); + case KeyExchangeAlgorithm.DHE_PSK: + case KeyExchangeAlgorithm.ECDHE_PSK: + case KeyExchangeAlgorithm.PSK: + case KeyExchangeAlgorithm.RSA_PSK: + return CreatePskKeyExchange(keyExchangeAlgorithm); + + case KeyExchangeAlgorithm.ECDH_anon: + case KeyExchangeAlgorithm.ECDH_ECDSA: + case KeyExchangeAlgorithm.ECDH_RSA: + return CreateECDHKeyExchange(keyExchangeAlgorithm); + + case KeyExchangeAlgorithm.ECDHE_ECDSA: + return CreateECDheKeyExchange(keyExchangeAlgorithm); + + default: + /* + * Note: internal error here; the TlsProtocol implementation verifies that the + * server-selected cipher suite was in the list of client-offered cipher suites, so if + * we now can't produce an implementation, we shouldn't have offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + private BigInteger ConvertBigNum(CBORObject cbor) + { + byte[] rgb = cbor.GetByteString(); + byte[] rgb2 = new byte[rgb.Length + 2]; + rgb2[0] = 0; + rgb2[1] = 0; + for (int i = 0; i < rgb.Length; i++) rgb2[i + 2] = rgb[i]; + + return new BigInteger(rgb2); + } + + protected TlsSignerCredentials GetECDsaSignerCredentials() + { +#if SUPPORT_RPK + if (_rawPublicKey != null) { + OneKey k = _rawPublicKey; + if (k.HasKeyType((int)COSE.GeneralValuesInt.KeyType_EC2) && + k.HasAlgorithm(COSE.AlgorithmValues.ECDSA_256)) { + + X9ECParameters p = k.GetCurve(); + ECDomainParameters parameters = new ECDomainParameters(p.Curve, p.G, p.N, p.H); + ECPrivateKeyParameters privKey = new ECPrivateKeyParameters("ECDSA", ConvertBigNum(k[CoseKeyParameterKeys.EC_D]), parameters); + + ECPoint point = k.GetPoint(); + ECPublicKeyParameters param = new ECPublicKeyParameters(point, parameters); + + SubjectPublicKeyInfo spi = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(param); + + return new DefaultTlsSignerCredentials(mContext, new RawPublicKey(spi), privKey, new SignatureAndHashAlgorithm(HashAlgorithm.sha256, SignatureAlgorithm.ecdsa)); + } + } +#endif + + TlsEvent e = new TlsEvent(TlsEvent.EventCode.SignCredentials) { + CipherSuite = KeyExchangeAlgorithm.ECDH_ECDSA + }; + + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(this, e); } + + if (e.SignerCredentials != null) return e.SignerCredentials; + throw new TlsFatalAlert(AlertDescription.internal_error); } /// @@ -101,41 +206,159 @@ public override void NotifySecureRenegotiation(bool secureRenegotiation) } - - internal class MyTlsAuthentication : TlsAuthentication { private readonly TlsContext _mContext; + public EventHandler TlsEventHandler; + private OneKey _rawPublicKey; + private KeySet _serverKeys; - internal MyTlsAuthentication(TlsContext context) + internal MyTlsAuthentication(TlsContext context, OneKey rawPublicKey) { this._mContext = context; + _rawPublicKey = rawPublicKey; } + public OneKey AuthenticationKey { get; private set; } + #if SUPPORT_RPK - public virtual void NotifyServerCertificate(AbstractCertificate x) + + public virtual void NotifyServerCertificate(AbstractCertificate serverCertificate) + { + if (serverCertificate is RawPublicKey) { + GetRpkKey((RawPublicKey)serverCertificate); + } + else { + TlsEvent e = new TlsEvent(TlsEvent.EventCode.ServerCertificate) { + Certificate = serverCertificate + }; + + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(this, e); + } + + if (!e.Processed) { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + } + } + + public void GetRpkKey(RawPublicKey rpk) { + AsymmetricKeyParameter key; + + try { + key = PublicKeyFactory.CreateKey(rpk.SubjectPublicKeyInfo()); + } + catch (Exception e) { + throw new TlsFatalAlert(AlertDescription.unsupported_certificate, e); + } + + if (key is ECPublicKeyParameters) { + ECPublicKeyParameters ecKey = (ECPublicKeyParameters)key; + + string s = ecKey.AlgorithmName; + OneKey newKey = new OneKey(); + newKey.Add(CoseKeyKeys.KeyType, GeneralValues.KeyType_EC); + if (ecKey.Parameters.Curve.Equals(NistNamedCurves.GetByName("P-256").Curve)) { + newKey.Add(CoseKeyParameterKeys.EC_Curve, GeneralValues.P256); + } + + newKey.Add(CoseKeyParameterKeys.EC_X, CBORObject.FromObject(ecKey.Q.Normalize().XCoord.ToBigInteger().ToByteArrayUnsigned())); + newKey.Add(CoseKeyParameterKeys.EC_Y, CBORObject.FromObject(ecKey.Q.Normalize().YCoord.ToBigInteger().ToByteArrayUnsigned())); + + foreach (OneKey k in _serverKeys) { + if (k.Compare(newKey)) { + AuthenticationKey = k; + return; + } + } + } + else { + // throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + + TlsEvent ev = new TlsEvent(TlsEvent.EventCode.ServerCertificate) { + Certificate = rpk + }; + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(this, ev); + } + + if (!ev.Processed) { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } } + #endif public virtual void NotifyServerCertificate(Certificate serverCertificate) { -/* - X509CertificateStructure[] chain = serverCertificate.GetCertificateList(); - Console.WriteLine("DTLS client received server certificate chain of length " + chain.Length); - for (int i = 0; i != chain.Length; i++) { - X509CertificateStructure entry = chain[i]; - // TODO Create fingerprint based on certificate signature algorithm digest - Console.WriteLine(" fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " (" - + entry.Subject + ")"); + /* + X509CertificateStructure[] chain = serverCertificate.GetCertificateList(); + Console.WriteLine("DTLS client received server certificate chain of length " + chain.Length); + for (int i = 0; i != chain.Length; i++) { + X509CertificateStructure entry = chain[i]; + // TODO Create fingerprint based on certificate signature algorithm digest + Console.WriteLine(" fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " (" + + entry.Subject + ")"); + } + */ + TlsEvent e = new TlsEvent(TlsEvent.EventCode.ServerCertificate) { + Certificate = serverCertificate + }; + + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(this, e); + } + + if (!e.Processed) { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); } - */ } - + + private BigInteger ConvertBigNum(CBORObject cbor) + { + byte[] rgb = cbor.GetByteString(); + byte[] rgb2 = new byte[rgb.Length + 2]; + rgb2[0] = 0; + rgb2[1] = 0; + for (int i = 0; i < rgb.Length; i++) rgb2[i + 2] = rgb[i]; + + return new BigInteger(rgb2); + } + public virtual TlsCredentials GetClientCredentials(CertificateRequest certificateRequest) { + if (certificateRequest.CertificateTypes == null || + !Arrays.Contains(certificateRequest.CertificateTypes, ClientCertificateType.ecdsa_sign)) { + return null; + } + +#if SUPPORT_RPK + if (_rawPublicKey != null) { + OneKey k = _rawPublicKey; + if (k.HasKeyType((int)COSE.GeneralValuesInt.KeyType_EC2) && + k.HasAlgorithm(COSE.AlgorithmValues.ECDSA_256)) { + + X9ECParameters p = k.GetCurve(); + ECDomainParameters parameters = new ECDomainParameters(p.Curve, p.G, p.N, p.H); + ECPrivateKeyParameters privKey = new ECPrivateKeyParameters("ECDSA", ConvertBigNum(k[CoseKeyParameterKeys.EC_D]), parameters); + + ECPoint point = k.GetPoint(); + ECPublicKeyParameters param = new ECPublicKeyParameters(point, parameters); + + SubjectPublicKeyInfo spi = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(param); + + return new DefaultTlsSignerCredentials(_mContext, new RawPublicKey(spi), privKey, new SignatureAndHashAlgorithm(HashAlgorithm.sha256, SignatureAlgorithm.ecdsa)); + } + + } /* byte[] certificateTypes = certificateRequest.CertificateTypes; if (certificateTypes == null || !Arrays.Contains(certificateTypes, ClientCertificateType.rsa_sign)) @@ -144,7 +367,20 @@ public virtual TlsCredentials GetClientCredentials(CertificateRequest certificat return TlsTestUtilities.LoadSignerCredentials(mContext, certificateRequest.SupportedSignatureAlgorithms, SignatureAlgorithm.rsa, "x509-client.pem", "x509-client-key.pem"); */ - return null; +#endif + // If we did not fine appropriate signer credientials - ask for help + + TlsEvent e = new TlsEvent(TlsEvent.EventCode.SignCredentials) { + CipherSuite = KeyExchangeAlgorithm.ECDHE_ECDSA + }; + + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(this, e); + } + + if (e.SignerCredentials != null) return e.SignerCredentials; + throw new TlsFatalAlert(AlertDescription.internal_error); } diff --git a/CoAP.NET/DTLS/DTLSClientChannel.cs b/CoAP.NET/DTLS/DTLSClientChannel.cs index 8906446..6e3c238 100644 --- a/CoAP.NET/DTLS/DTLSClientChannel.cs +++ b/CoAP.NET/DTLS/DTLSClientChannel.cs @@ -24,6 +24,8 @@ internal class DTLSClientChannel : IChannel private UDPChannel _udpChannel; private readonly OneKey _userKey; + public EventHandler TlsEventHandler; + /// /// Create a client only channel and use a randomly assigned port on /// the client UDP port. @@ -179,6 +181,7 @@ public ISession GetSession(System.Net.EndPoint ep) // No session - create a new one. session = new DTLSSession(ipEndPoint, DataReceived, _userKey); + session.TlsEventHandler += OnTlsEvent; AddSession(session); @@ -192,6 +195,15 @@ public ISession GetSession(System.Net.EndPoint ep) return session; } + private void OnTlsEvent(Object o, TlsEvent e) + { + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(o, e); + } + + } + /// /// Send data through the DTLS channel to other side /// diff --git a/CoAP.NET/DTLS/DTLSClientEndPoint.cs b/CoAP.NET/DTLS/DTLSClientEndPoint.cs index 89b55b7..52034b1 100644 --- a/CoAP.NET/DTLS/DTLSClientEndPoint.cs +++ b/CoAP.NET/DTLS/DTLSClientEndPoint.cs @@ -14,6 +14,7 @@ namespace Com.AugustCellars.CoAP.DTLS /// public class DTLSClientEndPoint : CoAPEndPoint { + public EventHandler TlsEventHandler; /// /// Instantiates a new DTLS endpoint with the specific channel and configuration @@ -80,6 +81,7 @@ private DTLSClientEndPoint(DTLSClientChannel channel, ICoapConfig config) : base MessageEncoder = UdpCoapMesageEncoder; MessageDecoder = UdpCoapMessageDecoder; EndpointSchema = "coaps"; + channel.TlsEventHandler += OnTlsEvent; } /// @@ -102,5 +104,14 @@ static IMessageEncoder UdpCoapMesageEncoder() { return new Spec.MessageEncoder18(); } + + private void OnTlsEvent(Object o, TlsEvent e) + { + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(o, e); + } + + } } } diff --git a/CoAP.NET/DTLS/DTLSEndPoint.cs b/CoAP.NET/DTLS/DTLSEndPoint.cs index 8fbc202..cf634ac 100644 --- a/CoAP.NET/DTLS/DTLSEndPoint.cs +++ b/CoAP.NET/DTLS/DTLSEndPoint.cs @@ -52,6 +52,7 @@ public DTLSEndPoint(DTLSChannel channel, ICoapConfig config) : base(channel, con MessageEncoder = UdpCoapMesageEncoder; MessageDecoder = UdpCoapMessageDecoder; EndpointSchema = "coaps"; + channel.TlsEventHandler += OnTlsEvent; } static IMessageDecoder UdpCoapMessageDecoder(byte[] data) @@ -63,5 +64,16 @@ static IMessageEncoder UdpCoapMesageEncoder() { return new Spec.MessageEncoder18(); } + + public EventHandler TlsEventHandler; + + private void OnTlsEvent(Object o, TlsEvent e) + { + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(o, e); + } + } + } } diff --git a/CoAP.NET/DTLS/DTLSSession.cs b/CoAP.NET/DTLS/DTLSSession.cs index fddc27e..a5509fd 100644 --- a/CoAP.NET/DTLS/DTLSSession.cs +++ b/CoAP.NET/DTLS/DTLSSession.cs @@ -34,6 +34,8 @@ public class DTLSSession : ISecureSession private readonly EventHandler _sessionEvents; + public EventHandler TlsEventHandler; + /// /// List of event handlers to inform about session events. /// @@ -112,9 +114,17 @@ public void Connect(UDPChannel udpChannel) else { pskIdentity = new BasicTlsPskIdentity(new byte[0], _userKey[CoseKeyParameterKeys.Octet_k].GetByteString()); } - } + _client = new DtlsClient(null, pskIdentity); + } + else if (_userKey.HasKeyType((int) COSE.GeneralValuesInt.KeyType_EC2)) { + _client = new DtlsClient(null, _userKey); + } } - _client = new DtlsClient(null, pskIdentity); + else { + _client = new DtlsClient(null, pskIdentity); + } + + _client.TlsEventHandler += OnTlsEvent; DtlsClientProtocol clientProtocol = new DtlsClientProtocol(new SecureRandom()); @@ -142,6 +152,7 @@ public void Accept(UDPChannel udpChannel, byte[] message) DtlsServerProtocol serverProtocol = new DtlsServerProtocol(new SecureRandom()); DtlsServer server = new DtlsServer(_serverKeys, _userKeys); + server.TlsEventHandler += OnTlsEvent; _transport.UDPChannel = udpChannel; _transport.Receive(message); @@ -255,9 +266,11 @@ void StartListen() } } } - byte[] buf2 = new byte[size]; - Array.Copy(buf, buf2, size); - FireDataReceived(buf2, _ipEndPoint); + else { + byte[] buf2 = new byte[size]; + Array.Copy(buf, buf2, size); + FireDataReceived(buf2, _ipEndPoint); + } } } @@ -269,6 +282,15 @@ private void FireDataReceived(Byte[] data, System.Net.EndPoint ep) } } + private void OnTlsEvent(Object o, TlsEvent e) + { + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(o, e); + } + + } + private class OurTransport : DatagramTransport { private UDPChannel _udpChannel; diff --git a/CoAP.NET/DTLS/DtlsServer.cs b/CoAP.NET/DTLS/DtlsServer.cs index 76cce7f..2ab6f9b 100644 --- a/CoAP.NET/DTLS/DtlsServer.cs +++ b/CoAP.NET/DTLS/DtlsServer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; @@ -7,8 +8,16 @@ using Org.BouncyCastle.Crypto.Tls; using Com.AugustCellars.COSE; +using Org.BouncyCastle.Asn1.Nist; using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Math.EC; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.X509; +using PeterO.Cbor; namespace Com.AugustCellars.CoAP.DTLS { @@ -17,33 +26,51 @@ class DtlsServer : DefaultTlsServer private KeySet _serverKeys; private KeySet _userKeys; + public EventHandler TlsEventHandler; + internal DtlsServer(KeySet serverKeys, KeySet userKeys) { _serverKeys = serverKeys; _userKeys = userKeys; mPskIdentityManager = new MyIdentityManager(userKeys); + mPskIdentityManager.TlsEventHandler += OnTlsEvent; } - protected override ProtocolVersion MinimumVersion { get {return ProtocolVersion.DTLSv10;} } - protected override ProtocolVersion MaximumVersion { get {return ProtocolVersion.DTLSv12;} } + protected override ProtocolVersion MinimumVersion => ProtocolVersion.DTLSv10; + protected override ProtocolVersion MaximumVersion => ProtocolVersion.DTLSv12; + + public OneKey AuthenticationKey => mPskIdentityManager.AuthenticationKey; - public OneKey AuthenticationKey + // Chain all of our events to the next level up. + + private void OnTlsEvent(Object o, TlsEvent e) { - get => mPskIdentityManager.AuthenticationKey; + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(o, e); + } } protected override int[] GetCipherSuites() { - return new int[] { + int[] i = new int[] { CipherSuite.TLS_PSK_WITH_AES_128_CCM_8, - CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 + CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, + CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 }; -#if false - return Arrays.Concatenate(base.GetCipherSuites(), - new int[] { - CipherSuite.TLS_PSK_WITH_AES_128_CCM_8, - }); -#endif + + // Give the outside code a chance to change this. + + TlsEvent e = new TlsEvent(TlsEvent.EventCode.GetCipherSuites) { + IntValues = i + }; + + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(this, e); + } + + return e.IntValues; } public override void NotifyFallback(bool isFallback) @@ -63,28 +90,67 @@ public override TlsCredentials GetCredentials() int keyExchangeAlgorithm = TlsUtilities.GetKeyExchangeAlgorithm(mSelectedCipherSuite); switch (keyExchangeAlgorithm) { - case KeyExchangeAlgorithm.DHE_PSK: - case KeyExchangeAlgorithm.ECDHE_PSK: - case KeyExchangeAlgorithm.PSK: - return null; + case KeyExchangeAlgorithm.DHE_PSK: + case KeyExchangeAlgorithm.ECDHE_PSK: + case KeyExchangeAlgorithm.PSK: + return null; - case KeyExchangeAlgorithm.RSA_PSK: - return GetRsaEncryptionCredentials(); + case KeyExchangeAlgorithm.RSA_PSK: + return GetRsaEncryptionCredentials(); case KeyExchangeAlgorithm.ECDHE_ECDSA: return GetECDsaSignerCredentials(); - default: - /* Note: internal error here; selected a key exchange we don't implement! */ - throw new TlsFatalAlert(AlertDescription.internal_error); + default: + /* Note: internal error here; selected a key exchange we don't implement! */ + throw new TlsFatalAlert(AlertDescription.internal_error); } } + private BigInteger ConvertBigNum(CBORObject cbor) + { + byte[] rgb = cbor.GetByteString(); + byte[] rgb2 = new byte[rgb.Length + 2]; + rgb2[0] = 0; + rgb2[1] = 0; + for (int i = 0; i < rgb.Length; i++) rgb2[i + 2] = rgb[i]; + + return new BigInteger(rgb2); + } + protected override TlsSignerCredentials GetECDsaSignerCredentials() { - AsymmetricKeyParameter privateKey = null; +#if SUPPORT_RPK + foreach (OneKey k in _serverKeys) { + if (k.HasKeyType((int) COSE.GeneralValuesInt.KeyType_EC2) && + k.HasAlgorithm(COSE.AlgorithmValues.ECDSA_256)) { + + X9ECParameters p = k.GetCurve(); + ECDomainParameters parameters = new ECDomainParameters(p.Curve, p.G, p.N, p.H); + ECPrivateKeyParameters privKey = new ECPrivateKeyParameters("ECDSA", ConvertBigNum(k[CoseKeyParameterKeys.EC_D]), parameters); + + ECPoint point = k.GetPoint(); + ECPublicKeyParameters param = new ECPublicKeyParameters(point, parameters); + + SubjectPublicKeyInfo spi = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(param); + + return new DefaultTlsSignerCredentials(mContext, new RawPublicKey(spi), privKey, new SignatureAndHashAlgorithm(HashAlgorithm.sha256, SignatureAlgorithm.ecdsa) ); + } + } +#endif + + // If we did not fine appropriate signer credientials - ask for help - return new DefaultTlsSignerCredentials(null, new Certificate(new X509CertificateStructure[0]), privateKey); + TlsEvent e = new TlsEvent(TlsEvent.EventCode.SignCredentials) { + CipherSuite = KeyExchangeAlgorithm.ECDHE_ECDSA + }; + + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(this, e); + } + + if (e.SignerCredentials != null) return e.SignerCredentials; throw new TlsFatalAlert(AlertDescription.internal_error); } @@ -106,7 +172,7 @@ public override TlsKeyExchange GetKeyExchange() return CreateECDHKeyExchange(keyExchangeAlgorithm); case KeyExchangeAlgorithm.ECDHE_ECDSA: - return CreateECDHKeyExchange(keyExchangeAlgorithm); + return CreateECDheKeyExchange(keyExchangeAlgorithm); default: /* @@ -130,11 +196,104 @@ protected override TlsKeyExchange CreateECDHKeyExchange(int keyExchange) mServerECPointFormats); } -#if false - protected override TlsSignerCredentials GetECDsaSignerCredentials() +#if SUPPORT_RPK + public override byte GetClientCertificateType(byte[] certificateTypes) { - return TlsTestUtilities.LoadSignerCredentials(mContext, mSupportedSignatureAlgorithms, SignatureAlgorithm.rsa, - "x509-server.pem", "x509-server-key.pem"); + TlsEvent e = new TlsEvent(TlsEvent.EventCode.ClientCertType) { + Bytes = certificateTypes, + Byte = (byte) 0xff + }; + + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(this, e); + } + + if (e.Byte != 0xff) { + return e.Byte; + } + + foreach (byte type in certificateTypes) { + if (type == 2) return type; // Assume we only support Raw Public Key + } + + + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + + public override byte GetServerCertificateType(byte[] certificateTypes) + { + TlsEvent e = new TlsEvent(TlsEvent.EventCode.ServerCertType) { + Bytes = certificateTypes, + Byte = (byte)0xff + }; + + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(this, e); + } + + if (e.Byte != 0xff) { + return e.Byte; + } + + foreach (byte type in certificateTypes) { + if (type == 2) return type; // Assume we only support Raw Public Key + } + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } +#endif + + public override CertificateRequest GetCertificateRequest() + { + byte[] certificateTypes = new byte[]{ ClientCertificateType.rsa_sign, + ClientCertificateType.ecdsa_sign }; + + IList serverSigAlgs = null; + if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(mServerVersion)) { + serverSigAlgs = TlsUtilities.GetDefaultSupportedSignatureAlgorithms(); + } + + return new CertificateRequest(certificateTypes, serverSigAlgs, null); + } + +#if SUPPORT_RPK + public override void NotifyClientCertificate(AbstractCertificate clientCertificate) + { + if (clientCertificate is RawPublicKey) { + mPskIdentityManager.GetRpkKey((RawPublicKey) clientCertificate); + } + else { + TlsEvent e = new TlsEvent(TlsEvent.EventCode.ClientCertificate) { + Certificate = clientCertificate + }; + + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(this, e); + } + + if (!e.Processed) { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + } + } +#else + public override void NotifyClientCertificate(Certificate clientCertificate) + { + TlsEvent e = new TlsEvent(TlsEvent.EventCode.ClientCertificate) { + Certificate = clientCertificate + }; + + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(this, e); + } + + if (!e.Processed) { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + } #endif @@ -144,6 +303,7 @@ internal class MyIdentityManager : TlsPskIdentityManager { private KeySet _userKeys; + public EventHandler TlsEventHandler; internal MyIdentityManager(KeySet keys) { @@ -175,8 +335,76 @@ public virtual byte[] GetPsk(byte[] identity) } } } + + + TlsEvent e = new TlsEvent(TlsEvent.EventCode.UnknownPskName) { + PskName = identity + }; + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(this, e); + } + + if (e.KeyValue != null) { + if (e.KeyValue.HasKeyType((int) COSE.GeneralValuesInt.KeyType_Octet)) { + AuthenticationKey = e.KeyValue; + return (byte[]) e.KeyValue[CoseKeyParameterKeys.Octet_k].GetByteString().Clone(); + } + } + return null; } + +#if SUPPORT_RPK + public void GetRpkKey(RawPublicKey rpk) + { + AsymmetricKeyParameter key; + + try { + key = PublicKeyFactory.CreateKey(rpk.SubjectPublicKeyInfo()); + } + catch (Exception e) { + throw new TlsFatalAlert(AlertDescription.unsupported_certificate, e); + } + + if (key is ECPublicKeyParameters) { + ECPublicKeyParameters ecKey = (ECPublicKeyParameters) key; + + string s = ecKey.AlgorithmName; + OneKey newKey = new OneKey(); + newKey.Add(CoseKeyKeys.KeyType, GeneralValues.KeyType_EC); + if (ecKey.Parameters.Curve.Equals(NistNamedCurves.GetByName("P-256").Curve)) { + newKey.Add(CoseKeyParameterKeys.EC_Curve, GeneralValues.P256); + } + + newKey.Add(CoseKeyParameterKeys.EC_X, CBORObject.FromObject(ecKey.Q.Normalize().XCoord.ToBigInteger().ToByteArrayUnsigned())); + newKey.Add(CoseKeyParameterKeys.EC_Y, CBORObject.FromObject(ecKey.Q.Normalize().YCoord.ToBigInteger().ToByteArrayUnsigned())); + + foreach (OneKey k in _userKeys) { + if (k.Compare(newKey)) { + AuthenticationKey = k; + return; + } + } + } + else { + // throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + + TlsEvent ev = new TlsEvent(TlsEvent.EventCode.ClientCertificate) { + Certificate = rpk + }; + + EventHandler handler = TlsEventHandler; + if (handler != null) { + handler(this, ev); + } + + if (!ev.Processed) { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + } +#endif } } } diff --git a/CoAP.NET/DTLS/TlsEvent.cs b/CoAP.NET/DTLS/TlsEvent.cs new file mode 100644 index 0000000..53818f9 --- /dev/null +++ b/CoAP.NET/DTLS/TlsEvent.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Com.AugustCellars.COSE; +using Org.BouncyCastle.Crypto.Tls; + +namespace Com.AugustCellars.CoAP.DTLS +{ + /// + /// Tell event handlers that something iteresting has happened in the underlying TLS + /// code that they may want to respond to. + /// + public class TlsEvent + { + public enum EventCode + { + UnknownPskName = 1, + + // Get cipher suites - Change IntValues with array of suites + GetCipherSuites = 2, + + // Return a set of signer credentials + // Ciphersuite provides the cipher suite + SignCredentials = 3, + + + GetExtensions = 4, + + // Notify the UI what certificate has been provided by the other party + // Input: Certificate - the certificate + // Result: Processed - TRUE (if acceptable) + // will normally raise an exception if the certificate is not legal, but + // not if not processed. + ClientCertificate = 5, + ServerCertificate = 6, + + // Select certificate type from the list + // Input: Bytes contains the client supported values + // Result: Byte contains -1 if non supported else the selected value + + ServerCertType = 7, + ClientCertType = 8, + } + + public TlsEvent(EventCode code) + { + Code = code; + Processed = false; + } + + public EventCode Code { get; } + + public bool Processed { get; set; } + + /// + /// For code UnknownPskName - contains the name of the PSK + /// + public byte[] PskName { get; set; } + + /// + /// Return the key to be used for a PskName if you have one + /// + public OneKey KeyValue { get; set; } + + public int[] IntValues { get; set; } + + public int CipherSuite { get; set; } + public TlsSignerCredentials SignerCredentials { get; set; } + + public IDictionary Dictionary { get; set; } + +#if SUPPORT_RPK + public AbstractCertificate Certificate { get; set; } +#else + public Certificate Certificate { get; set; } +#endif + + public byte[] Bytes { get; set; } + public byte Byte { get; set; } + } +} diff --git a/CoAP.NET/Server/CoapServer.cs b/CoAP.NET/Server/CoapServer.cs index 82ea5d2..fe7da4a 100644 --- a/CoAP.NET/Server/CoapServer.cs +++ b/CoAP.NET/Server/CoapServer.cs @@ -26,6 +26,7 @@ public class CoapServer : IServer private static readonly ILogger _Log = LogManager.GetLogger(typeof(CoapServer)); private readonly IResource _root; private readonly List _endpoints = new List(); + private readonly System.Net.EndPoint _endPointSupplied; private IMessageDeliverer _deliverer; /// @@ -52,7 +53,12 @@ public CoapServer(params Int32[] ports) /// the configuration, or null for default /// the ports to bind to public CoapServer(ICoapConfig config, params Int32[] ports) - : this(config, null, ports) + : this(config, null, null, ports) + { + } + + public CoapServer(ICoapConfig config, System.Net.EndPoint endPoint, params Int32[] ports) + : this(config, null, endPoint, ports) { } @@ -79,6 +85,22 @@ public CoapServer(ICoapConfig config, IResource rootResource, params int[] ports } + public CoapServer(ICoapConfig config, IResource rootResource, System.Net.EndPoint endPoint, params int[] ports) + { + Config = config ?? CoapConfig.Default; + _root = rootResource ?? new RootResource(this); + _deliverer = new ServerMessageDeliverer(Config, _root); + + Resource wellKnown = new Resource(".well-known", false); + wellKnown.Add(new DiscoveryResource(_root)); + _root.Add(wellKnown); + + _endPointSupplied = endPoint; + foreach (int port in ports) { + Bind(port); + } + } + /// /// Return the configuration interface used by the server. /// @@ -86,7 +108,12 @@ public CoapServer(ICoapConfig config, IResource rootResource, params int[] ports private void Bind(Int32 port) { - AddEndPoint(new CoAPEndPoint(port, Config)); + if (_endPointSupplied != null) { + AddEndPoint(new CoAPEndPoint(new System.Net.IPEndPoint(((System.Net.IPEndPoint) _endPointSupplied).Address, port), Config)); + } + else { + AddEndPoint(new CoAPEndPoint(port, Config)); + } } /// diff --git a/CoAP.Test/DTLS/DtlsEvents.cs b/CoAP.Test/DTLS/DtlsEvents.cs new file mode 100644 index 0000000..af8ef76 --- /dev/null +++ b/CoAP.Test/DTLS/DtlsEvents.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Com.AugustCellars.CoAP.Log; +using Com.AugustCellars.CoAP.Net; +using Com.AugustCellars.CoAP.Server; +using Com.AugustCellars.CoAP.Server.Resources; +using Com.AugustCellars.COSE; +using NUnit.Framework; + +using PeterO.Cbor; + +namespace Com.AugustCellars.CoAP.DTLS +{ + [TestFixture] + class DtlsEvents + { + private static OneKey PskOne; + private static OneKey PskTwo; + private static KeySet UserKeys; + + private CoapServer _server; + private HelloResource _resource; + private int _serverPort; + + private static readonly byte[] PskOneName = Encoding.UTF8.GetBytes("KeyOne"); + private static readonly byte[] PskTwoName = Encoding.UTF8.GetBytes("KeyTwo"); + + [OneTimeSetUp] + public void OneTimeSetup() + { + PskOne = new OneKey(); + PskOne.Add(CoseKeyKeys.KeyType, GeneralValues.KeyType_Octet); + PskOne.Add(CoseKeyKeys.KeyIdentifier, CBORObject.FromObject(PskOneName)); + PskOne.Add(CoseKeyParameterKeys.Octet_k, CBORObject.FromObject(Encoding.UTF8.GetBytes("abcDEFghiJKL"))); + + PskTwo = new OneKey(); + PskTwo.Add(CoseKeyKeys.KeyType, GeneralValues.KeyType_Octet); + PskTwo.Add(CoseKeyKeys.KeyIdentifier, CBORObject.FromObject(PskTwoName)); + PskTwo.Add(CoseKeyParameterKeys.Octet_k, CBORObject.FromObject(Encoding.UTF8.GetBytes("12345678091234"))); + + UserKeys = new KeySet(); + // UserKeys.AddKey(PskOne); + // UserKeys.AddKey(PskTwo); + } + + [SetUp] + public void SetupServer() + { + Log.LogManager.Level = LogLevel.Fatal; + CreateServer(); + } + + [TearDown] + public void ShutdownServer() + { + _server.Dispose(); + } + + [Test] + public void DtlsTestPskEvents() + { + Uri uri = new Uri($"coaps://localhost:{_serverPort}/Hello1"); + DTLSClientEndPoint client = new DTLSClientEndPoint(PskOne); + client.Start(); + + Request req = new Request(Method.GET) { + URI = uri, + EndPoint = client + }; + + req.Send(); + Response resp = req.WaitForResponse(50000); + Assert.AreEqual(null, resp); + client.Stop(); + + DTLSClientEndPoint client2 = new DTLSClientEndPoint(PskTwo); + client2.Start(); + Request req2 = new Request(Method.GET) { + URI = uri, + EndPoint = client2 + }; + + req2.Send(); + string txt = req2.WaitForResponse(50000).ResponseText; + Assert.AreEqual("Hello from KeyTwo", txt); + + client2.Stop(); + + Thread.Sleep(5000); + + } + + + private void CreateServer() + { + DTLSEndPoint endpoint = new DTLSEndPoint(null, UserKeys, 0); + _resource = new HelloResource("Hello1"); + _server = new CoapServer(); + _server.Add(_resource); + + _server.AddEndPoint(endpoint); + endpoint.TlsEventHandler += ServerEventHandler; + _server.Start(); + _serverPort = ((System.Net.IPEndPoint)endpoint.LocalEndPoint).Port; + } + + private static void ServerEventHandler(Object o, TlsEvent e) + { + switch (e.Code) { + case TlsEvent.EventCode.UnknownPskName: + if (e.PskName.SequenceEqual(PskOneName)) { + // We don't recognize this name + } + else if (e.PskName.SequenceEqual(PskTwoName)) { + e.KeyValue = PskTwo; + } + break; + } + } + + class HelloResource : Resource + { + public HelloResource(String name) : base(name) + { + + } + + protected override void DoGet(CoapExchange exchange) + { + String content = $"Hello from "; + + content += Encoding.UTF8.GetString(exchange.Request.TlsContext.AuthenticationKey[CoseKeyKeys.KeyIdentifier].GetByteString()); + + exchange.Respond(content); + } + } + } +}