Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: crypto: include support for DTLS in stdlib or x/crypto #46915

Closed
daenney opened this issue Jun 24, 2021 · 22 comments
Closed

proposal: crypto: include support for DTLS in stdlib or x/crypto #46915

daenney opened this issue Jun 24, 2021 · 22 comments
Labels
FrozenDueToAge Proposal Proposal-Crypto Proposal related to crypto packages or other security issues
Milestone

Comments

@daenney
Copy link

daenney commented Jun 24, 2021

The purpose of this proposal is to renew a discussion around potentially
including support for DTLS in either the Go standard library or x/crypto.

A proposal for this was filed in 2015 as #13525 and closed in 2018. It's now 3
to 6 years later so I'm looking to revisit that. A lot of things have changed
during that time. Even if we end up coming to the same conclusion, I believe
there's value for the community in clarifying the Go team's position on whether
they feel they are now properly staffed, able and willing to take on such an
endeavour.

I would also like to explore a possibility of having DTLS remain a third-party
package but somehow being able to make greater use of the crypto/tls machinery.

What is DTLS

DTLS, or Datagram Transport Layer Security, is the UDP equivalent of TLS. It
needs to exist because UDP is stateless and unreliable, but in order to
negotiate the parameters of an encrypted connection we need to temporarily
create some form of reliable, in order, exchange of packets.

For the purpose of completing the TLS handshake, DTLS extends the TLS handshake
so we can handle the problems of packet reordering and loss. Once the handshake
is completed, DTLS is no more reliable than using UDP. The difference at that
point is that the application payload is encrypted going forward.

Due to this, DTLS isn't specified as a complete protocol by itself, but as a
set of diffs "on top of" TLS.

Where it is used

DTLS is seeing more and more adoption as more protocols are being standardised
that leverage UDP as their transport layer instead of TCP. In the past half a
decade or so that has meant at least:

  • Web Real-Time Communication family of RFCs
  • Constrained Application Protocol family of RFCs
  • Internet Protocol Flow Information Export family of RFCs

WebRTC is used extensively for modern day-to-day audio/video conferencing. CoAP
is used across a wide range of IoT use-cases, both personal (like IKEA's TRÅDFRI
gateway and Philips Hue) as well as in industrial applications. It is currently
also in use in low-bandwidth Matrix.

DTLS also sees wide spread usage as part of security products such as VPNs like
Cisco AnyConnect, in things like Microsoft Remote Desktop, or in IPFIX exporters
implemented on top of VMware's Go library. It's also found its way into other
projects like RouteDNS and coder.com.

Though these are some of the more common or obvious uses of DTLS that folks might
run into in their day-to-day, DTLS is used in a lot more places. There are RFCs
for using DTLS to secure things from DNS, RTP, certificate provisioning
through EST, the Babel routing protocol, RELOAD, RADIUS, MPLS-in-UDP, GRE-in-UDP or
things like a DTLS tunneling protocol for use in multimedia conferences, using
DTLS with SCTP and the list goes on. That's just what the IETF is discussing.

The current state

As the previous section aimed to illustrate, more and more communication is by default
built on top of encrypted transports and the share of new protocols building on top of UDP
is increasing. Older protocols built on UDP are being updated to define how to secure them
using DTLS. DTLS is no longer a niche thing, but has become part of foundational protocols
that power planet-scale communication today and in the future.

The lack of DTLS support in Go has seen it go underused in these areas. This is
unfortunate as Go is a common choice for writing protocol implementations and systems
that communicate over networks, while providing stronger safety guarantees compared
to other commonly used languages in this area.

The community has responded to this gap in two manners. Some have built
cgo wrappers around OpenSSL. But cgo is not Go, and ensuring we transit safely
across the cgo boundary while handling sensitive data is tricky. It complicates
debugging these systems and complicates cross-compiling for different platforms.
Many of these wrappers are experimental, incomplete and have gone unmaintained since
their creation, making them almost certainly dangerous to use in practice.

The second approach has been to implenment DTLS natively in Go. This is what the
Pion project did, which was orginally started with the aim of being able to build
out a WebRTC ecosystem in Go. It has since then been adopted by others, like the
go-coap project which provides a CoAP implementation for Go.

pion/dtls is maintained by a team of volunteer FLOSS maintainers. Even though we're
not rolling our own crypto and despite the fact that everyone should be able to
implement DTLS by following the RFCs, there is a meaningful gap between "being a
competent developer" and "having years of pratical experience dealing with crypto"
and potentially a formal education to back that up. Due to our limited resources,
responding to security issues can also be tricky for us. Because of this and the
fact that the library doesn't hold any kind of official status, the Go community
has understandably been weary of building on top of it.

Possible futures

I'd like to consider if we can change the current status quo. Bar some entities
being able to finanically support Pion so that we can have people work on this
full-time and get funding for audits, I can see roughly three outcomes:

  • Nothing changes. The Go maintainers don't have the resources to devote spending
    time on this and/or continue to feel that the lack of DTLS support isn't holding
    back Go. pion/dtls continues to be maintained as is by the current team of volunteers,
    as their time and resources permit.

  • The Go maintainers decide that providing DTLS support through the Go standard libary
    or x/crypto would be meaningful to the community and would help further Go's adoption
    in certain areas. The necessary code to support DTLS makes it upstream and
    pion/dtls enters deprecation until all supported versions of Go ship with DTLS (in
    case of inclusion in the standard library) or becomes an adapter for a hypothetical
    x/crypto/dtls to ease transition.

  • Since DTLS is specified as a set of diffs or extensions of TLS, it stands to reason
    it might be possible to take the same approach with crypto/tls. It might be possible
    to provide the necessary extension points from crypto/tls to allow a DTLS library
    to remain as a third-party package that is able to affect the handshake while being
    able to reuse the rest of crypto/tls. It's unclear right now if this would be
    possible, and if the effort involved to do so and any potential long-term maintenance
    burden on both the Go and Pion teams would be worth it.

  • .. other options the Go team might propose ..

Acknowledgements

No matter the outcome here, I'd like to extend a heartfelt thank you to the Go
maintainers. While building pion/dtls we've been able to learn a lot by taking cues
from crypto/tls. It remains a source of inspiration and reference to us. We regularly
use it to improve ourselves, and when we need to weigh supporting a feature or not
and what the API should look like we always try to converge on what crypto/tls does.

We want to make it clear that even if nothing changes, the Pion authors do intend to
continue and maintain our DTLS implementation in the future for as long as we can.
But we're re-raising this proposal to see if we'll have to do so indefinitely.

Thank you @Sean-Der for reviewing this proposal.

@gopherbot gopherbot added this to the Proposal milestone Jun 24, 2021
@Sean-Der
Copy link

Thanks for making this happen @daenney! If this does get accepted DTLS I would
love to join the maintainers.

IMO value is lost having it under the Pion organization. We have our own rules/culture that isn't benefiting
DTLS. The community we have right now is all around RTC, so it tends to drown out DTLS people.

I have also had a few people not interested in getting involved/contributing because it is under Pion. Maybe
people think that Pion is a company and not just an Open Source project?

@ianlancetaylor
Copy link
Contributor

CC @FiloSottile @katiehockman

@ianlancetaylor ianlancetaylor changed the title proposal: Include support for DTLS in stdlib or x/crypto proposal: include support for DTLS in stdlib or x/crypto Jun 25, 2021
@ianlancetaylor ianlancetaylor added the Proposal-Crypto Proposal related to crypto packages or other security issues label Jun 25, 2021
@ianlancetaylor ianlancetaylor added this to Incoming in Proposals (old) Jun 25, 2021
@rsc
Copy link
Contributor

rsc commented Nov 3, 2021

cc @julieqiu

@rsc
Copy link
Contributor

rsc commented Nov 3, 2021

This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group

@rsc rsc moved this from Incoming to Active in Proposals (old) Nov 3, 2021
@rsc
Copy link
Contributor

rsc commented Dec 1, 2021

@rsc rsc changed the title proposal: include support for DTLS in stdlib or x/crypto proposal: crypto: include support for DTLS in stdlib or x/crypto Dec 1, 2021
@rsc
Copy link
Contributor

rsc commented Jan 5, 2022

cc @golang/security

@rsc
Copy link
Contributor

rsc commented Mar 2, 2022

Talked to the security team about this. It's a large effort, estimated to increase TLS maintenance burden by 20%, but it's also potentially important for a large fraction of the Go ecosystem. We are not staffed to take this on at the moment. But it sounds like the team behind pion/tls is entirely volunteer, which is not ideal either from a staffing perspective. The path ahead is unclear on this one.

Perhaps we can work out what the API would look like. Is it small, or is it big? What would it look like?

@Sean-Der
Copy link

Sean-Der commented Mar 4, 2022

@rsc The API for pion/dtls is crypto/tls except it does a net.PacketConn instead of net.Conn. I think it would be good to do the same. crypto/tls is well designed and has done us a lot of good mimicking it.

Should I submit a proposal? @daenney and I would love to do that work if a well designed DTLS implementation has a high probability of getting merged. I am hesitant to take time away from other work if it is unlikely.

@rsc
Copy link
Contributor

rsc commented Mar 9, 2022

If you can just paste the API additions relative to crypto/tls into this thread, that would probably be the right next step. Thanks!

@Sean-Der
Copy link

The following is a diff of go doc -all crypto/tls

This brings all the functionality that pion/dtls have today. The API isn't an exact match, in some places we made API mistakes. Below the diff I also summarized the features. @daenney @at-wat @bocajim @jkralik Anything I have missed/couldn't support your use case on?

@@ -49,6 +49,18 @@
 
     See https://www.iana.org/assignments/tls-parameters/tls-parameters.xml
 
+
+type ExtendedMasterSecretType int
+    ExtendedMasterSecretType declares the policy the client and server will
+    follow for the Extended Master Secret extension
+
+const (
+  RequestExtendedMasterSecret ExtendedMasterSecretType = iota
+  RequireExtendedMasterSecret
+  DisableExtendedMasterSecret
+)
+    ExtendedMasterSecretType enums
+
 const (
 	VersionTLS10 = 0x0301
 	VersionTLS11 = 0x0302
@@ -79,9 +91,36 @@
     must be non-nil and must include at least one certificate or else set
     GetCertificate.
 
+func Resume(state *State, conn net.PacketConn, config *Config) (*Conn, error)
+    Resume imports an already established dtls connection using a specific dtls
+    state
 
 TYPES
 
+type CipherSuite interface {
+  // String of CipherSuite, only used for logging
+  String() string
+
+  // ID of CipherSuite.
+  ID() CipherSuiteID
+
+  // What type of Certificate does this CipherSuite use
+  CertificateType() clientcertificate.Type
+
+  // What Hash function is used during verification
+  HashFunc() func() hash.Hash
+
+  // AuthenticationType controls what authentication method is using during the handshake
+  AuthenticationType() CipherSuiteAuthenticationType
+
+  // Called when keying material has been generated, should initialize the internal cipher
+  Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error
+  IsInitialized() bool
+  Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error)
+  Decrypt(in []byte) ([]byte, error)
+}
+    CipherSuite is an interface that all DTLS CipherSuites must satisfy
+
 type Certificate struct {
 	Certificate [][]byte
 	// PrivateKey contains the private key corresponding to the public key in
@@ -246,10 +285,10 @@
 	// might be rejected if used.
 	SupportedVersions []uint16
 
-	// Conn is the underlying net.Conn for the connection. Do not read
+	// PacketConn is the underlying net.PacketConn for the connection. Do not read
 	// from, or write to, this connection; that will cause the TLS
 	// connection to fail.
-	Conn net.Conn
+	PacketConn net.PacketConn
 
 	// Has unexported fields.
 }
@@ -305,6 +344,15 @@
     sessions.
 
 type Config struct {
+  // CustomCipherSuites is a list of CipherSuites that can be
+  // provided by the user. This allow users to user Ciphers that are reserved
+  // for private usage.
+  CustomCipherSuites func() []CipherSuite
+
+  // ExtendedMasterSecret determines if the "Extended Master Secret" extension
+  // should be disabled, requested, or required (default requested).
+  ExtendedMasterSecret ExtendedMasterSecretType
+
 	// Rand provides the source of entropy for nonces and RSA blinding.
 	// If Rand is nil, TLS uses the cryptographic random reader in package
 	// crypto/rand.
@@ -504,6 +552,11 @@
 	// used for debugging.
 	KeyLogWriter io.Writer
 
+  // PSK sets the pre-shared key used by this DTLS connection
+  // If PSK is non-nil only PSK CipherSuites will be used
+  PSK             PSKCallback
+  PSKIdentityHint []byte
+
 	// Has unexported fields.
 }
     A Config structure is used to configure a TLS client or server. After one
@@ -541,10 +594,10 @@
 type Conn struct {
 	// Has unexported fields.
 }
-    A Conn represents a secured connection. It implements the net.Conn
+    A Conn represents a secured connection. It implements the net.PacketConn
     interface.
 
-func Client(conn net.Conn, config *Config) *Conn
+func Client(conn net.PacketConn, config *Config) *Conn
     Client returns a new TLS client side connection using conn as the underlying
     transport. The config cannot be nil: users must set either ServerName or
     InsecureSkipVerify in the config.
@@ -567,7 +620,7 @@
     DialWithDialer uses context.Background internally; to specify the context,
     use Dialer.DialContext with NetDialer set to the desired dialer.
 
-func Server(conn net.Conn, config *Config) *Conn
+func Server(conn net.PacketConn, config *Config) *Conn
     Server returns a new TLS server side connection using conn as the underlying
     transport. The configuration config must be non-nil and must include at
     least one certificate or else set GetCertificate.
@@ -612,6 +665,10 @@
     OCSPResponse returns the stapled OCSP response from the TLS server, if any.
     (Only valid for client connections.)
 
+func (c *Conn) SelectedSRTPProtectionProfile() (SRTPProtectionProfile, bool)
+    SelectedSRTPProtectionProfile returns the selected SRTP Protection Profile and if one
+		was succesfully negotiated.
+
 func (c *Conn) Read(b []byte) (int, error)
     Read reads data from the connection.
 
@@ -713,6 +770,12 @@
 	// RFC 7627, and https://mitls.org/pages/attacks/3SHAKE#channelbindings.
 	TLSUnique []byte
 
+	// IdentityHint which may be sent by the server for PSK
+  IdentityHint     []byte
+
+
+  SessionID        []byte
+
 	// Has unexported fields.
 }
     ConnectionState records basic TLS details about the connection.
@@ -723,6 +786,12 @@
     the seed. If the connection was set to allow renegotiation via
     Config.Renegotiation, this function will return an error.
 
+func (s *State) MarshalBinary() ([]byte, error)
+    MarshalBinary is a binary.BinaryMarshaler.MarshalBinary implementation
+
+func (s *State) UnmarshalBinary(data []byte) error
+    UnmarshalBinary is a binary.BinaryUnmarshaler.UnmarshalBinary implementation
+
 type CurveID uint16
     CurveID is the type of a TLS identifier for an elliptic curve. See
     https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-8.
@@ -753,7 +822,7 @@
     Dialer dials TLS connections given a configuration and a Dialer for the
     underlying connection.
 
-func (d *Dialer) Dial(network, addr string) (net.Conn, error)
+func (d *Dialer) Dial(network, addr string) (net.PacketConn, error)
     Dial connects to the given network address and initiates a TLS handshake,
     returning the resulting TLS connection.
 
@@ -762,7 +831,7 @@
     Dial uses context.Background internally; to specify the context, use
     DialContext.
 
-func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error)
+func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.PacketConn, error)
     DialContext connects to the given network address and initiates a TLS
     handshake, returning the resulting TLS connection.
 
@@ -778,11 +847,11 @@
 	// RecordHeader contains the five bytes of TLS record header that
 	// triggered the error.
 	RecordHeader [5]byte
-	// Conn provides the underlying net.Conn in the case that a client
+	// Conn provides the underlying net.PacketConn in the case that a client
 	// sent an initial handshake that didn't look like TLS.
 	// It is nil if there's already been a handshake or a TLS alert has
 	// been written to the connection.
-	Conn net.Conn
+	Conn net.PacketConn
 }
     RecordHeaderError is returned when a TLS record header is invalid.
 
@@ -845,3 +914,17 @@
 )
 func (i SignatureScheme) String() string
 
+type SRTPProtectionProfile uint16
+		SRTPProtectionProfile defines the parameters and options that are in effect
+		for the SRTP processing https://tools.ietf.org/html/rfc5764#section-4.1.2
+
+const (
+	SRTPProtectionProfile_AES128_CM_HMAC_SHA1_80 SRTPProtectionProfile = 0x0001
+	SRTPProtectionProfile_AES128_CM_HMAC_SHA1_32 SRTPProtectionProfile = 0x0002
+	SRTPProtectionProfile_AEAD_AES_128_GCM       SRTPProtectionProfile = 0x0007
+	SRTPProtectionProfile_AEAD_AES_256_GCM       SRTPProtectionProfile = 0x0008
+)
+
+type PSKCallback func([]byte) ([]byte, error)
+    PSKCallback is called once we have the remote's PSKIdentityHint. If the
+    remote provided none it will be nil

  • Ability to require (or disable) extended master secret.
  • PSK support
  • Ability to implement Custom/Reserved Cipher Suite. Some IoT devices define their own suites/Needed for [CoAP](https://en.wikipedia.org/wiki/Constrained_Application_Protocol]
  • SRTP Extension. Just finds a intersection of uint16 between client/server
  • Use a net.PacketConn instead of net.Conn
  • ConnectionState can be serialized/deserialized

@rsc
Copy link
Contributor

rsc commented Mar 16, 2022

Thanks for the API. Presumably those changed method signatures would become new methods, like PacketClient, etc.

@rolandshoemaker or anyone else, do these look like plausible API additions for crypto/tls to support DTLS?

@Sean-Der
Copy link

@rolandshoemaker For a good first step I was hoping to use the existing crypto/tls implementation. I am totally happy with losing features if that means a better DTLS implementation.

We could have an additional layer that handles the DTLS Specific header for loss/reordering/fragmentation. Nothing in crypo/tls would need to change.

@bocajim
Copy link

bocajim commented Mar 18, 2022

Here are some comments from my perspective... First, as background, I created github.com/qwerty-iot/dtls (formerly github.com/bocajim/dtls) which is used in several IoT platforms as the DTLS server. It was never intended to have any compatibility with crypto/tls, so it is not a good target for consideration, but it does identify some key requirements for the IoT community.

If I have to summarize the use case in a sentence, it would go like this: We require a solution for "millions of DTLS sessions that communicate once or twice per day that is compatible with high-availability deployments".

  1. Pre-shared key support

  2. Specifically the most common cipher for the standards we use is TLS_PSK_WITH_AES_128_CCM_8 (0xC0A8)

  3. Ability to export/import DTLS sessions via callbacks. There are three main use cases:

  • First is to age sessions from RAM to disk when not in use (think about 25M sessions used once per day).
  • The second use case is exporting session information so it can be stored in a centralized cache such that devices can communicate with any server in a cluster without re-negotiating the session.
  • Third is to export sessions at server shutdown and restore them on startup such that maintenance can be performed without requiring devices to re-handshake to the server.
  1. Support for DTLS connection ID (https://tools.ietf.org/id/draft-ietf-tls-dtls-connection-id-02.html) (I'm not sure how other stacks are supporting this when the RFC has yet to be approved)

  2. Limit use of any "per-session" go routines, again consider 25M active sessions with one packet per day, the memory requirements are too high if there are routines for each session blocked on reads.

We have two corporate sponsors for qwerty-iot/dtls, so we have funding to support a long-term solution, but it would need to align with our core requirements.

I also support improvements to crypto/tls to remove the amount of overlapping code in the current DTLS implementations.

@Sean-Der
Copy link

Hey @bocajim I didn't realize pion/dtls didn't have the features you needed. Are you blocked on 5?

It might make more sense to keep things in userland if DTLS has intractable requirements by users?

@bocajim
Copy link

bocajim commented Mar 21, 2022

@Sean-Der I can't say if my use case is mainstream or fringe, so I wouldn't want to scuttle efforts to standardize a DTLS implementation if my use case is outside of the core needs of developers. But given the high usage of DTLS within the IoT community, specifically the LPWA community, I think there will be more demand over time for a highly scalable stack.

The pion/dtls package is the most complete implementation in Go, and I tried to rewrite parts of it to maintain the same interface but scale in a less resource intensive way, but it became too much work for my team. The qwerty-iot/dtls library is missing lots of little elements of the DTLS RFC, but does check the box for high scalability, so maybe there is some work we can do to try to bring the two concepts together.

@FiloSottile
Copy link
Contributor

FiloSottile commented Mar 21, 2022 via email

@bocajim
Copy link

bocajim commented Mar 21, 2022

@FiloSottile This is a definite problem, once supported, general lifetime of a device could be as much as 10 years where compatibility needs to be maintained regardless of security concerns or advances. This is generally easy to manage by letting the server implementation choose which ciphers/extensions to support, so it shouldnt be a problem.

The hardest part to manage is that have somewhere between 3-5 hacks in my implementation for clients that do not adhere to the DTLS specification correctly, but the device vendors do not have the ability to update their clients, so we are stuck supporting their poor implementation on the server side. I don't see being able to do these types of hacks in the standard library.

@daenney
Copy link
Author

daenney commented Mar 24, 2022

IoT isn't the only use-case of DTLS as we've shown in the proposal. I'd like to be careful about overindexing on "IoT is turrible, we shouldn't have to deal with it in stdlib". There are many places where DTLS is used where we are not constrained by poor vendor implementations that can't be updated. Having an implementation in stdlib also doesn't preclude anyone maintaining their own with speciality hacks if their needs call for it.

@bocajim
Copy link

bocajim commented Mar 24, 2022

Absolutely agree, just make sure we highlight the intended use cases and the expected gaps so there are no delusions about the applicability of the end result.

IoT isn't the only use-case of DTLS as we've shown in the proposal. I'd like to be careful about overindexing on "IoT is turrible, we shouldn't have to deal with it in stdlib". There are many places where DTLS is used where we are not constrained by poor vendor implementations that can't be updated. Having an implementation in stdlib also doesn't preclude anyone maintaining their own with speciality hacks if their needs call for it.

@rsc
Copy link
Contributor

rsc commented Apr 6, 2022

It sounds like maybe there's no clear reason (not enough usage) for the Go team to pick up and maintain this, and it sounds like people don't use pion/dtls because of concerns about maintainer responsiveness too. So it sounds like we're not going to solve that, and maybe the right answer is to keep using pion/dtls or leave space for someone else to step forward with more maintenance?

@daenney
Copy link
Author

daenney commented Apr 7, 2022

Very well. Lets leave it at that then.

@daenney daenney closed this as completed Apr 7, 2022
@rsc rsc moved this from Active to Declined in Proposals (old) Apr 13, 2022
@rsc
Copy link
Contributor

rsc commented Apr 13, 2022

This proposal has been declined as retracted.
— rsc for the proposal review group

@golang golang locked and limited conversation to collaborators Apr 13, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge Proposal Proposal-Crypto Proposal related to crypto packages or other security issues
Projects
No open projects
Development

No branches or pull requests

7 participants