summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crypto/src/tls/AbstractTlsClient.cs28
-rw-r--r--crypto/src/tls/AbstractTlsServer.cs26
-rw-r--r--crypto/src/tls/DtlsClientProtocol.cs18
-rw-r--r--crypto/src/tls/DtlsServerProtocol.cs18
-rw-r--r--crypto/src/tls/SecurityParameters.cs9
-rw-r--r--crypto/src/tls/TlsExtensionsUtilities.cs28
6 files changed, 126 insertions, 1 deletions
diff --git a/crypto/src/tls/AbstractTlsClient.cs b/crypto/src/tls/AbstractTlsClient.cs
index 3061f3642..af53e9fbf 100644
--- a/crypto/src/tls/AbstractTlsClient.cs
+++ b/crypto/src/tls/AbstractTlsClient.cs
@@ -77,6 +77,15 @@ namespace Org.BouncyCastle.Tls
                 throw new TlsFatalAlert(AlertDescription.illegal_parameter);
         }
 
+        /// <summary>RFC 9146 DTLS connection ID.</summary>
+        /// <remarks>
+        /// The default <see cref="GetClientExtensions"/> implementation calls this to get the connection_id extension
+        /// the client will send. As future communication doesn't include the connection IDs length, this should either
+        /// be fixed-length or include the connection ID's length. (see explanation in RFC 9146 4. "cid:")
+        /// </remarks>
+        /// <returns>The connection ID to use.</returns>
+        protected virtual byte[] GetNewConnectionID() => null;
+
         /// <exception cref="IOException"/>
         public virtual TlsPskIdentity GetPskIdentity()
         {
@@ -239,11 +248,13 @@ namespace Org.BouncyCastle.Tls
 
             bool offeringTlsV13Plus = false;
             bool offeringPreTlsV13 = false;
+            bool offeringDtlsV12 = false;
             {
                 ProtocolVersion[] supportedVersions = GetProtocolVersions();
                 for (int i = 0; i < supportedVersions.Length; ++i)
                 {
-                    if (TlsUtilities.IsTlsV13(supportedVersions[i]))
+                    var supportedVersion = supportedVersions[i];
+                    if (TlsUtilities.IsTlsV13(supportedVersion))
                     {
                         offeringTlsV13Plus = true;
                     }
@@ -251,6 +262,8 @@ namespace Org.BouncyCastle.Tls
                     {
                         offeringPreTlsV13 = true;
                     }
+
+                    offeringDtlsV12 |= ProtocolVersion.DTLSv12.Equals(supportedVersion);
                 }
             }
 
@@ -371,6 +384,19 @@ namespace Org.BouncyCastle.Tls
                 TlsExtensionsUtilities.AddServerCertificateTypeExtensionClient(clientExtensions, serverCertTypes);
             }
 
+            if (offeringDtlsV12)
+            {
+                /*
+                 * RFC 9146 3. When a DTLS session is resumed or renegotiated, the "connection_id" extension is
+                 * negotiated afresh.
+                 */
+                var clientConnectionID = GetNewConnectionID();
+                if (clientConnectionID != null)
+                {
+                    TlsExtensionsUtilities.AddConnectionIDExtension(clientExtensions, clientConnectionID);
+                }
+            }
+
             return clientExtensions;
         }
 
diff --git a/crypto/src/tls/AbstractTlsServer.cs b/crypto/src/tls/AbstractTlsServer.cs
index 3c62793b6..9f107d905 100644
--- a/crypto/src/tls/AbstractTlsServer.cs
+++ b/crypto/src/tls/AbstractTlsServer.cs
@@ -217,6 +217,16 @@ namespace Org.BouncyCastle.Tls
             return null;
         }
 
+        /// <summary>RFC 9146 DTLS connection ID.</summary>
+        /// <remarks>
+        /// This method will be called if a connection_id extension was sent by the client.
+        /// If the return value is non-null, the server will send this connection ID to the client to use in future packets.
+        /// As future communication doesn't include the connection IDs length, this should either be fixed-length
+        /// or include the connection ID's length. (see explanation in RFC 9146 4. "cid:")
+        /// </remarks>
+        /// <returns>The connection ID to use.</returns>
+        protected virtual byte[] GetNewConnectionID() => null;
+
         public virtual void Init(TlsServerContext context)
         {
             this.m_context = context;
@@ -587,6 +597,22 @@ namespace Org.BouncyCastle.Tls
             {
                 TlsExtensionsUtilities.AddAlpnExtensionServer(serverExtensions, m_selectedProtocolName);
             }
+
+            if (ProtocolVersion.DTLSv12.Equals(m_context.ServerVersion))
+            {
+                /*
+                 * RFC 9146 3. When a DTLS session is resumed or renegotiated, the "connection_id" extension is
+                 * negotiated afresh.
+                 */
+                if (m_clientExtensions.ContainsKey(ExtensionType.connection_id))
+                {
+                    var serverConnectionID = GetNewConnectionID();
+                    if (serverConnectionID != null)
+                    {
+                        TlsExtensionsUtilities.AddConnectionIDExtension(m_serverExtensions, serverConnectionID);
+                    }
+                }
+            }
         }
 
         public virtual IList<SupplementalDataEntry> GetServerSupplementalData()
diff --git a/crypto/src/tls/DtlsClientProtocol.cs b/crypto/src/tls/DtlsClientProtocol.cs
index 3b32c044e..88a077168 100644
--- a/crypto/src/tls/DtlsClientProtocol.cs
+++ b/crypto/src/tls/DtlsClientProtocol.cs
@@ -825,6 +825,24 @@ namespace Org.BouncyCastle.Tls
                 state.serverExtensions);
             securityParameters.m_applicationProtocolSet = true;
 
+            // Connection ID
+            if (ProtocolVersion.DTLSv12.Equals(securityParameters.NegotiatedVersion))
+            {
+                /*
+                 * RFC 9146 3. When a DTLS session is resumed or renegotiated, the "connection_id" extension is
+                 * negotiated afresh.
+                 */
+                var serverConnectionID = TlsExtensionsUtilities.GetConnectionIDExtension(state.serverExtensions);
+                if (serverConnectionID != null)
+                {
+                    var clientConnectionID = TlsExtensionsUtilities.GetConnectionIDExtension(state.clientExtensions)
+                        ?? throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                    securityParameters.m_connectionIDLocal = serverConnectionID;
+                    securityParameters.m_connectionIDPeer = clientConnectionID;
+                }
+            }
+
             // Heartbeats
             {
                 HeartbeatExtension heartbeatExtension = TlsExtensionsUtilities.GetHeartbeatExtension(
diff --git a/crypto/src/tls/DtlsServerProtocol.cs b/crypto/src/tls/DtlsServerProtocol.cs
index 3bf92395b..cf16bc302 100644
--- a/crypto/src/tls/DtlsServerProtocol.cs
+++ b/crypto/src/tls/DtlsServerProtocol.cs
@@ -549,6 +549,24 @@ namespace Org.BouncyCastle.Tls
                 state.serverExtensions);
             securityParameters.m_applicationProtocolSet = true;
 
+            // Connection ID
+            if (ProtocolVersion.DTLSv12.Equals(securityParameters.NegotiatedVersion))
+            {
+                /*
+                 * RFC 9146 3. When a DTLS session is resumed or renegotiated, the "connection_id" extension is
+                 * negotiated afresh.
+                 */
+                var serverConnectionID = TlsExtensionsUtilities.GetConnectionIDExtension(state.serverExtensions);
+                if (serverConnectionID != null)
+                {
+                    var clientConnectionID = TlsExtensionsUtilities.GetConnectionIDExtension(state.clientExtensions)
+                        ?? throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                    securityParameters.m_connectionIDLocal = clientConnectionID;
+                    securityParameters.m_connectionIDPeer = serverConnectionID;
+                }
+            }
+
             /*
              * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore
              * extensions appearing in the client hello, and send a server hello containing no
diff --git a/crypto/src/tls/SecurityParameters.cs b/crypto/src/tls/SecurityParameters.cs
index 7775ca7c7..7deeeb72b 100644
--- a/crypto/src/tls/SecurityParameters.cs
+++ b/crypto/src/tls/SecurityParameters.cs
@@ -57,6 +57,11 @@ namespace Org.BouncyCastle.Tls
         internal byte[] m_localVerifyData = null;
         internal byte[] m_peerVerifyData = null;
 
+        /// <summary>Connection ID we use during communication to the peer.</summary>
+        internal byte[] m_connectionIDLocal;
+        /// <summary>Connection ID our peer uses for communication to us.</summary>
+        internal byte[] m_connectionIDPeer;
+
         internal void Clear()
         {
             this.m_sessionHash = null;
@@ -135,6 +140,10 @@ namespace Org.BouncyCastle.Tls
             get { return m_clientSupportedGroups; }
         }
 
+        public byte[] ConnectionIDLocal => m_connectionIDLocal;
+
+        public byte[] ConnectionIDPeer => m_connectionIDPeer;
+
         public TlsSecret EarlyExporterMasterSecret
         {
             get { return m_earlyExporterMasterSecret; }
diff --git a/crypto/src/tls/TlsExtensionsUtilities.cs b/crypto/src/tls/TlsExtensionsUtilities.cs
index 836c1b506..bc4ca2714 100644
--- a/crypto/src/tls/TlsExtensionsUtilities.cs
+++ b/crypto/src/tls/TlsExtensionsUtilities.cs
@@ -64,6 +64,12 @@ namespace Org.BouncyCastle.Tls
         }
 
         /// <exception cref="IOException"/>
+        public static void AddConnectionIDExtension(IDictionary<int, byte[]> extensions, byte[] connectionID)
+        {
+            extensions[ExtensionType.connection_id] = CreateConnectionIDExtension(connectionID);
+        }
+
+        /// <exception cref="IOException"/>
         public static void AddCookieExtension(IDictionary<int, byte[]> extensions, byte[] cookie)
         {
             extensions[ExtensionType.cookie] = CreateCookieExtension(cookie);
@@ -317,6 +323,13 @@ namespace Org.BouncyCastle.Tls
         }
 
         /// <exception cref="IOException"/>
+        public static byte[] GetConnectionIDExtension(IDictionary<int, byte[]> extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.connection_id);
+            return extensionData == null ? null : ReadConnectionIDExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
         public static byte[] GetCookieExtension(IDictionary<int, byte[]> extensions)
         {
             byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.cookie);
@@ -631,6 +644,15 @@ namespace Org.BouncyCastle.Tls
         }
 
         /// <exception cref="IOException"/>
+        public static byte[] CreateConnectionIDExtension(byte[] connectionID)
+        {
+            if (connectionID == null)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return TlsUtilities.EncodeOpaque8(connectionID);
+        }
+
+        /// <exception cref="IOException"/>
         public static byte[] CreateCookieExtension(byte[] cookie)
         {
             if (TlsUtilities.IsNullOrEmpty(cookie) || cookie.Length >= (1 << 16))
@@ -1059,6 +1081,12 @@ namespace Org.BouncyCastle.Tls
         }
 
         /// <exception cref="IOException"/>
+        public static byte[] ReadConnectionIDExtension(byte[] extensionData)
+        {
+            return TlsUtilities.DecodeOpaque8(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
         public static byte[] ReadCookieExtension(byte[] extensionData)
         {
             return TlsUtilities.DecodeOpaque16(extensionData, 1);