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);
|