From 412557e8d81b0ece4d02541622e6aa9c9efd0f82 Mon Sep 17 00:00:00 2001 From: Jo Turk Date: Tue, 30 Jun 2026 14:21:01 +0300 Subject: [PATCH 001/365] Relax TestContextConfig cancel timing --- conn_go_test.go | 74 ++++++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/conn_go_test.go b/conn_go_test.go index bb2719ace..9582d516d 100644 --- a/conn_go_test.go +++ b/conn_go_test.go @@ -50,8 +50,7 @@ func TestContextConfig(t *testing.T) { //nolint:cyclop } dials := map[string]struct { - f func() (func() (net.Conn, error), func()) - order []byte + f func() (func() (net.Conn, error), func()) }{ "Dial": { f: func() (func() (net.Conn, error), func()) { @@ -68,7 +67,6 @@ func TestContextConfig(t *testing.T) { //nolint:cyclop cancel() } }, - order: []byte{0, 1, 2}, }, "Client": { f: func() (func() (net.Conn, error), func()) { @@ -87,7 +85,6 @@ func TestContextConfig(t *testing.T) { //nolint:cyclop cancel() } }, - order: []byte{0, 1, 2}, }, "Server": { f: func() (func() (net.Conn, error), func()) { @@ -106,50 +103,65 @@ func TestContextConfig(t *testing.T) { //nolint:cyclop cancel() } }, - order: []byte{0, 1, 2}, }, } + type dialResult struct { + err error + ok bool + completedAt time.Time + } + for name, dial := range dials { t.Run(name, func(t *testing.T) { - done := make(chan struct{}) + done := make(chan dialResult, 1) + startedAt := time.Now() go func() { d, cancel := dial.f() conn, err := d() defer cancel() var netError net.Error - if !errors.As(err, &netError) || !netError.Temporary() { //nolint:staticcheck - assert.Fail(t, "Dial failed with unexpected error", "err: %v", err) - close(done) - - return - } - done <- struct{}{} if err == nil { _ = conn.Close() } - }() - var order []byte - early := time.After(20 * time.Millisecond) - late := time.After(60 * time.Millisecond) - func() { - for len(order) < 3 { - select { - case <-early: - order = append(order, 0) - case _, ok := <-done: - if !ok { - return - } - order = append(order, 1) - case <-late: - order = append(order, 2) - } + done <- dialResult{ + err: err, + ok: errors.As(err, &netError) && netError.Temporary(), //nolint:staticcheck + completedAt: time.Now(), } }() - assert.Equal(t, dial.order, order, "Invalid cancel timing") + + const earlyCancelWindow = 20 * time.Millisecond + time.Sleep(earlyCancelWindow) + + assertResult := func(result dialResult) { + assert.GreaterOrEqual( + t, + result.completedAt.Sub(startedAt), + earlyCancelWindow, + "Invalid cancel timing", + ) + if !result.ok { + assert.Fail(t, "Dial failed with unexpected error", "err: %v", result.err) + } + } + + select { + case result := <-done: + assertResult(result) + + return + default: + } + + select { + case result := <-done: + assertResult(result) + case <-time.After(time.Second): + assert.Fail(t, "Dial did not finish after context cancellation") + } }) } } From 8ed75388de253eaeac3667eb4c0843555a0c6d33 Mon Sep 17 00:00:00 2001 From: Jo Turk Date: Tue, 30 Jun 2026 15:27:16 +0300 Subject: [PATCH 002/365] Allow close_notify on test assertion --- conn_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/conn_test.go b/conn_test.go index 53eba1599..c6dda23a2 100644 --- a/conn_test.go +++ b/conn_test.go @@ -888,7 +888,10 @@ func TestPSKServerKeyExchange(t *testing.T) { //nolint:cyclop receivedServerKeyExchange := gotServerKeyExchange.Load() assert.NoError(t, server.Close()) - assert.NoError(t, <-clientErr, "TestPSK: Client erro") + if err := <-clientErr; err != nil { + assert.ErrorIs(t, err, &alertError{&alert.Alert{Level: alert.Warning, Description: alert.CloseNotify}}, + "TestPSK: Client error") + } assert.Equal(t, expectedServerKeyExchange, receivedServerKeyExchange) }) From 60e739a857c3b29d2e7453932040edb260ac4122 Mon Sep 17 00:00:00 2001 From: Sean DuBois Date: Thu, 25 Jun 2026 09:59:09 -0400 Subject: [PATCH 003/365] Move flighthandlers to internal --- certificate.go | 125 +------ certificate_test.go | 18 +- cipher_suite.go | 157 +++----- cipher_suite_test.go | 10 +- config.go | 2 - conn.go | 344 +++++++++++------- conn_test.go | 9 +- crypto_test.go | 75 ++-- flight.go | 104 ------ flight13_test_helpers_test.go | 77 ++++ flight6handler.go | 99 ----- flight_13.go | 194 ---------- flighthandler.go | 81 ----- flighthandler_13.go | 61 ---- flighthandlers_client_13_test.go | 181 ++++----- flighthandlers_server_13_test.go | 241 ++++++------ handshake_cache.go | 235 ------------ handshake_cache_test.go | 227 ++++++------ handshake_transcript_13_test.go | 4 +- handshaker.go | 172 ++++----- handshaker_13.go | 86 +++-- handshaker_13_test.go | 106 +++--- handshaker_test.go | 166 ++++----- internal/ciphersuite/ciphersuite.go | 100 +++++ internal/config/config.go | 65 ---- internal/config/handshake.go | 270 ++++++++++++++ internal/flight/cache.go | 223 ++++++++++++ .../flight/flight12/flight0handler.go | 79 ++-- .../flight/flight12/flight1handler.go | 94 ++--- .../flight/flight12/flight1handler_test.go | 44 +-- .../flight/flight12/flight2handler.go | 32 +- .../flight/flight12/flight3handler.go | 156 ++++---- .../flight/flight12/flight4bhandler.go | 74 ++-- .../flight/flight12/flight4handler.go | 202 +++++----- .../flight/flight12/flight4handler_test.go | 38 +- .../flight/flight12/flight5bhandler.go | 50 +-- .../flight/flight12/flight5handler.go | 197 +++++----- internal/flight/flight12/flight6handler.go | 102 ++++++ .../flight12/flight_test_helpers_test.go | 51 +++ internal/flight/flight12/flighthandler12.go | 106 ++++++ internal/flight/flight13/flighthandler13.go | 114 ++++++ .../flight13/flighthandlers_client_13.go | 179 ++++----- .../flight13/flighthandlers_server_13.go | 100 ++--- internal/flight/helpers.go | 48 +++ internal/flight/types.go | 120 ++++++ .../handshakecrypto/crypto.go | 29 +- options.go | 43 ++- options_test.go | 63 ++-- packet.go | 15 - state.go | 4 +- ...st.go => supported_elliptic_curves_test.go | 0 util.go | 35 +- 52 files changed, 2842 insertions(+), 2565 deletions(-) delete mode 100644 flight.go create mode 100644 flight13_test_helpers_test.go delete mode 100644 flight6handler.go delete mode 100644 flight_13.go delete mode 100644 flighthandler.go delete mode 100644 flighthandler_13.go delete mode 100644 handshake_cache.go delete mode 100644 internal/config/config.go create mode 100644 internal/config/handshake.go create mode 100644 internal/flight/cache.go rename flight0handler.go => internal/flight/flight12/flight0handler.go (69%) rename flight1handler.go => internal/flight/flight12/flight1handler.go (63%) rename flight1handler_test.go => internal/flight/flight12/flight1handler_test.go (89%) rename flight2handler.go => internal/flight/flight12/flight2handler.go (69%) rename flight3handler.go => internal/flight/flight12/flight3handler.go (65%) rename flight4bhandler.go => internal/flight/flight12/flight4bhandler.go (61%) rename flight4handler.go => internal/flight/flight12/flight4handler.go (66%) rename flight4handler_test.go => internal/flight/flight12/flight4handler_test.go (81%) rename flight5bhandler.go => internal/flight/flight12/flight5bhandler.go (52%) rename flight5handler.go => internal/flight/flight12/flight5handler.go (55%) create mode 100644 internal/flight/flight12/flight6handler.go create mode 100644 internal/flight/flight12/flight_test_helpers_test.go create mode 100644 internal/flight/flight12/flighthandler12.go create mode 100644 internal/flight/flight13/flighthandler13.go rename flighthandlers_client_13.go => internal/flight/flight13/flighthandlers_client_13.go (71%) rename flighthandlers_server_13.go => internal/flight/flight13/flighthandlers_server_13.go (79%) create mode 100644 internal/flight/helpers.go create mode 100644 internal/flight/types.go rename crypto.go => internal/handshakecrypto/crypto.go (94%) delete mode 100644 packet.go rename flight3handler_test.go => supported_elliptic_curves_test.go (100%) diff --git a/certificate.go b/certificate.go index 0dce71287..39b68aa06 100644 --- a/certificate.go +++ b/certificate.go @@ -4,13 +4,9 @@ package dtls import ( - "bytes" "crypto/tls" - "crypto/x509" - "fmt" - "strings" - dtlserrors "github.com/pion/dtls/v3/internal/errors" + dtlsconfig "github.com/pion/dtls/v3/internal/config" "github.com/pion/dtls/v3/pkg/protocol/handshake" ) @@ -47,122 +43,5 @@ type CertificateRequestInfo struct { // NOTE: original src: // https://github.com/golang/go/blob/29b9a328d268d53833d2cc063d1d8b4bf6852675/src/crypto/tls/common.go#L1273 func (cri *CertificateRequestInfo) SupportsCertificate(c *tls.Certificate) error { - if len(cri.AcceptableCAs) == 0 { - return nil - } - - for j, cert := range c.Certificate { - x509Cert := c.Leaf - // Parse the certificate if this isn't the leaf node, or if - // chain.Leaf was nil. - if j != 0 || x509Cert == nil { - var err error - if x509Cert, err = x509.ParseCertificate(cert); err != nil { - return fmt.Errorf("failed to parse certificate #%d in the chain: %w", j, err) - } - } - - for _, ca := range cri.AcceptableCAs { - if bytes.Equal(x509Cert.RawIssuer, ca) { - return nil - } - } - } - - return dtlserrors.ErrNotAcceptableCertificateChain -} - -func (c *handshakeConfig) setNameToCertificateLocked() { - nameToCertificate := make(map[string]*tls.Certificate) - for i := range c.localCertificates { - cert := &c.localCertificates[i] - x509Cert := cert.Leaf - if x509Cert == nil { - var parseErr error - x509Cert, parseErr = x509.ParseCertificate(cert.Certificate[0]) - if parseErr != nil { - continue - } - } - if len(x509Cert.Subject.CommonName) > 0 { - nameToCertificate[strings.ToLower(x509Cert.Subject.CommonName)] = cert - } - for _, san := range x509Cert.DNSNames { - nameToCertificate[strings.ToLower(san)] = cert - } - } - c.nameToCertificate = nameToCertificate -} - -//nolint:cyclop -func (c *handshakeConfig) getCertificate(clientHelloInfo *ClientHelloInfo) (*tls.Certificate, error) { - c.mu.Lock() - defer c.mu.Unlock() - - if c.localGetCertificate != nil && - (len(c.localCertificates) == 0 || len(clientHelloInfo.ServerName) > 0) { - cert, err := c.localGetCertificate(clientHelloInfo) - if cert != nil || err != nil { - return cert, err - } - } - - if c.nameToCertificate == nil { - c.setNameToCertificateLocked() - } - - if len(c.localCertificates) == 0 { - return nil, dtlserrors.ErrNoCertificates - } - - if len(c.localCertificates) == 1 { - // There's only one choice, so no point doing any work. - return &c.localCertificates[0], nil - } - - if len(clientHelloInfo.ServerName) == 0 { - return &c.localCertificates[0], nil - } - - name := strings.TrimRight(strings.ToLower(clientHelloInfo.ServerName), ".") - - if cert, ok := c.nameToCertificate[name]; ok { - return cert, nil - } - - // try replacing labels in the name with wildcards until we get a - // match. - labels := strings.Split(name, ".") - for i := range labels { - labels[i] = "*" - candidate := strings.Join(labels, ".") - if cert, ok := c.nameToCertificate[candidate]; ok { - return cert, nil - } - } - - // If nothing matches, return the first certificate. - return &c.localCertificates[0], nil -} - -// NOTE: original src: -// https://github.com/golang/go/blob/29b9a328d268d53833d2cc063d1d8b4bf6852675/src/crypto/tls/handshake_client.go#L974 -func (c *handshakeConfig) getClientCertificate(cri *CertificateRequestInfo) (*tls.Certificate, error) { - c.mu.Lock() - defer c.mu.Unlock() - if c.localGetClientCertificate != nil { - return c.localGetClientCertificate(cri) - } - - for i := range c.localCertificates { - chain := c.localCertificates[i] - if err := cri.SupportsCertificate(&chain); err != nil { - continue - } - - return &chain, nil - } - - // No acceptable certificate found. Don't send a certificate. - return new(tls.Certificate), nil + return dtlsconfig.SupportsCertificate(cri.AcceptableCAs, c) } diff --git a/certificate_test.go b/certificate_test.go index 8c294d8a0..e8b6fc510 100644 --- a/certificate_test.go +++ b/certificate_test.go @@ -7,6 +7,7 @@ import ( "crypto/tls" "testing" + dtlsconfig "github.com/pion/dtls/v3/internal/config" "github.com/pion/dtls/v3/pkg/crypto/selfsign" "github.com/stretchr/testify/assert" ) @@ -80,11 +81,22 @@ func TestGetCertificate(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { + getCertificate := func(info *dtlsconfig.ClientHelloInfo) (*tls.Certificate, error) { + return test.getCertificate(&ClientHelloInfo{ + ServerName: info.ServerName, + CipherSuites: info.CipherSuites, + RandomBytes: info.RandomBytes, + }) + } + if test.getCertificate == nil { + getCertificate = nil + } + cfg := &handshakeConfig{ - localCertificates: test.localCertificates, - localGetCertificate: test.getCertificate, + LocalCertificates: test.localCertificates, + LocalGetCertificate: getCertificate, } - cert, err := cfg.getCertificate(&ClientHelloInfo{ServerName: test.serverName}) + cert, err := cfg.GetCertificate(&dtlsconfig.ClientHelloInfo{ServerName: test.serverName}) assert.NoError(t, err) assert.Equal(t, test.expectedCertificate.Leaf, cert.Leaf, "Certificate Leaf should match expected") }) diff --git a/cipher_suite.go b/cipher_suite.go index 9e85d6328..27384bc3c 100644 --- a/cipher_suite.go +++ b/cipher_suite.go @@ -11,7 +11,6 @@ import ( "crypto/tls" "fmt" "hash" - "slices" "github.com/pion/dtls/v3/internal/ciphersuite" dtlserrors "github.com/pion/dtls/v3/internal/errors" @@ -126,7 +125,7 @@ const VersionDTLS13 = 0xfefc // Our implementation differs slightly in that it takes in a CipherSuiteID, // like the rest of our library, instead of a uint16 like crypto/tls. func CipherSuiteName(id CipherSuiteID) string { - suite := cipherSuiteForID(id, nil) + suite := cipherSuiteForID(id) if suite != nil { return suite.String() } @@ -168,59 +167,8 @@ func InsecureCipherSuites() []*tls.CipherSuite { // Taken from https://www.iana.org/assignments/tls-parameters/tls-parameters.xml // A cipherSuite is a specific combination of key agreement, cipher and MAC // function. -func cipherSuiteForID(id CipherSuiteID, customCiphers func() []CipherSuite) CipherSuite { //nolint:cyclop - switch id { //nolint:exhaustive - case TLS_AES_128_GCM_SHA256: - return ciphersuite.NewTLSAes128GcmSha256() - case TLS_AES_256_GCM_SHA384: - return ciphersuite.NewTLSAes256GcmSha384() - case TLS_CHACHA20_POLY1305_SHA256: - return ciphersuite.NewTLSChacha20Poly1305Sha256() - case TLS_ECDHE_ECDSA_WITH_AES_128_CCM: - return ciphersuite.NewTLSEcdheEcdsaWithAes128Ccm() - case TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: - return ciphersuite.NewTLSEcdheEcdsaWithAes128Ccm8() - case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: - return &ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{} - case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: - return &ciphersuite.TLSEcdheRsaWithAes128GcmSha256{} - case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: - return &ciphersuite.TLSEcdheEcdsaWithAes256CbcSha{} - case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: - return &ciphersuite.TLSEcdheRsaWithAes256CbcSha{} - case TLS_PSK_WITH_AES_128_CCM: - return ciphersuite.NewTLSPskWithAes128Ccm() - case TLS_PSK_WITH_AES_128_CCM_8: - return ciphersuite.NewTLSPskWithAes128Ccm8() - case TLS_PSK_WITH_AES_256_CCM_8: - return ciphersuite.NewTLSPskWithAes256Ccm8() - case TLS_PSK_WITH_AES_128_GCM_SHA256: - return &ciphersuite.TLSPskWithAes128GcmSha256{} - case TLS_PSK_WITH_AES_128_CBC_SHA256: - return &ciphersuite.TLSPskWithAes128CbcSha256{} - case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: - return &ciphersuite.TLSEcdheEcdsaWithAes256GcmSha384{} - case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: - return &ciphersuite.TLSEcdheRsaWithAes256GcmSha384{} - case TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256: - return ciphersuite.NewTLSEcdhePskWithAes128CbcSha256() - case TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: - return &ciphersuite.TLSEcdheEcdsaWithChacha20Poly1305Sha256{} - case TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: - return &ciphersuite.TLSEcdheRsaWithChacha20Poly1305Sha256{} - case TLS_PSK_WITH_CHACHA20_POLY1305_SHA256: - return &ciphersuite.TLSPskWithChacha20Poly1305Sha256{} - } - - if customCiphers != nil { - for _, c := range customCiphers() { - if c.ID() == id { - return c - } - } - } - - return nil +func cipherSuiteForID(id CipherSuiteID) CipherSuite { + return ciphersuite.ForID(id, nil) } // TLS 1.3 CipherSuites we support in order of preference. @@ -278,6 +226,15 @@ func cipherSuiteIDs(cipherSuites []CipherSuite) []uint16 { return rtrn } +func configCipherSuiteIDs(cipherSuites []ciphersuite.CipherSuite) []uint16 { + rtrn := []uint16{} + for _, c := range cipherSuites { + rtrn = append(rtrn, uint16(c.ID())) + } + + return rtrn +} + func defaultCipherSuitesForVersions(minVersion, maxVersion protocol.Version) []CipherSuite { cipherSuites := []CipherSuite{} for _, version := range supportedVersionsRange(minVersion, maxVersion) { @@ -292,59 +249,16 @@ func defaultCipherSuitesForVersions(minVersion, maxVersion protocol.Version) []C return cipherSuites } -func knownCipherSuiteSupportedVersions(id CipherSuiteID) ([]protocol.Version, bool) { - switch id { //nolint:exhaustive - case TLS_AES_128_GCM_SHA256, - TLS_AES_256_GCM_SHA384, - TLS_CHACHA20_POLY1305_SHA256: - return []protocol.Version{protocol.Version1_3}, true - case TLS_ECDHE_ECDSA_WITH_AES_128_CCM, - TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - TLS_PSK_WITH_AES_128_CCM, - TLS_PSK_WITH_AES_128_CCM_8, - TLS_PSK_WITH_AES_256_CCM_8, - TLS_PSK_WITH_AES_128_GCM_SHA256, - TLS_PSK_WITH_AES_128_CBC_SHA256, - TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256, - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - TLS_PSK_WITH_CHACHA20_POLY1305_SHA256: - return []protocol.Version{protocol.Version1_2}, true - default: - return nil, false - } -} - func cipherSuiteSupportedVersions(id CipherSuiteID) []protocol.Version { - if versions, ok := knownCipherSuiteSupportedVersions(id); ok { - return versions - } - - return []protocol.Version{protocol.Version1_2} + return ciphersuite.SupportedVersions(id) } func cipherSuiteSupportedVersionIDs(id CipherSuiteID) []uint16 { - versions := cipherSuiteSupportedVersions(id) - ids := make([]uint16, 0, len(versions)) - for _, version := range versions { - ids = append(ids, protocolVersionID(version)) - } - - return ids -} - -func protocolVersionID(version protocol.Version) uint16 { - return uint16(version.Major)<<8 | uint16(version.Minor) + return ciphersuite.SupportedVersionIDs(id) } func cipherSuiteIDSupportsVersion(id CipherSuiteID, version protocol.Version) bool { - return slices.ContainsFunc(cipherSuiteSupportedVersions(id), version.Equal) + return ciphersuite.IDSupportsVersion(id, version) } func cipherSuiteIDSupportsVersions(id CipherSuiteID, minVersion, maxVersion protocol.Version) bool { @@ -357,8 +271,11 @@ func cipherSuiteIDSupportsVersions(id CipherSuiteID, minVersion, maxVersion prot return false } -func filterCipherSuitesForVersion(cipherSuites []CipherSuite, version protocol.Version) []CipherSuite { - filtered := make([]CipherSuite, 0, len(cipherSuites)) +func filterCipherSuitesForVersion( + cipherSuites []ciphersuite.CipherSuite, + version protocol.Version, +) []ciphersuite.CipherSuite { + filtered := make([]ciphersuite.CipherSuite, 0, len(cipherSuites)) for _, c := range cipherSuites { if cipherSuiteIDSupportsVersion(c.ID(), version) { filtered = append(filtered, c) @@ -369,10 +286,10 @@ func filterCipherSuitesForVersion(cipherSuites []CipherSuite, version protocol.V } func filterCipherSuitesForVersions( - cipherSuites []CipherSuite, + cipherSuites []ciphersuite.CipherSuite, minVersion, maxVersion protocol.Version, -) []CipherSuite { - filtered := make([]CipherSuite, 0, len(cipherSuites)) +) []ciphersuite.CipherSuite { + filtered := make([]ciphersuite.CipherSuite, 0, len(cipherSuites)) for _, c := range cipherSuites { if cipherSuiteIDSupportsVersions(c.ID(), minVersion, maxVersion) { filtered = append(filtered, c) @@ -387,7 +304,7 @@ func parseCipherSuites( userSelectedSuites []CipherSuiteID, customCipherSuites func() []CipherSuite, includeCertificateSuites, includePSKSuites bool, -) ([]CipherSuite, error) { +) ([]ciphersuite.CipherSuite, error) { return parseCipherSuitesForVersions( userSelectedSuites, customCipherSuites, @@ -404,11 +321,11 @@ func parseCipherSuitesForVersions( customCipherSuites func() []CipherSuite, includeCertificateSuites, includePSKSuites bool, minVersion, maxVersion protocol.Version, -) ([]CipherSuite, error) { - cipherSuitesForIDs := func(ids []CipherSuiteID) ([]CipherSuite, error) { - cipherSuites := []CipherSuite{} +) ([]ciphersuite.CipherSuite, error) { + cipherSuitesForIDs := func(ids []CipherSuiteID) ([]ciphersuite.CipherSuite, error) { + cipherSuites := []ciphersuite.CipherSuite{} for _, id := range ids { - c := cipherSuiteForID(id, nil) + c := cipherSuiteForID(id) if c == nil { return nil, &invalidCipherSuiteError{id} } @@ -419,7 +336,7 @@ func parseCipherSuitesForVersions( } var ( - cipherSuites []CipherSuite + cipherSuites []ciphersuite.CipherSuite err error i int ) @@ -429,12 +346,17 @@ func parseCipherSuitesForVersions( return nil, err } } else { - cipherSuites = defaultCipherSuitesForVersions(minVersion, maxVersion) + cipherSuites = toConfigCipherSuites(defaultCipherSuitesForVersions(minVersion, maxVersion)) } // Put CustomCipherSuites before ID selected suites if customCipherSuites != nil { - cipherSuites = append(customCipherSuites(), cipherSuites...) + custom := customCipherSuites() + configCipherSuites := make([]ciphersuite.CipherSuite, 0, len(custom)+len(cipherSuites)) + for _, cipherSuite := range custom { + configCipherSuites = append(configCipherSuites, cipherSuite) + } + cipherSuites = append(configCipherSuites, cipherSuites...) } cipherSuites = filterCipherSuitesForVersions(cipherSuites, minVersion, maxVersion) @@ -470,7 +392,10 @@ func parseCipherSuitesForVersions( return cipherSuites[:i], nil } -func filterCipherSuitesForCertificate(cert *tls.Certificate, cipherSuites []CipherSuite) []CipherSuite { +func filterCipherSuitesForCertificate( + cert *tls.Certificate, + cipherSuites []ciphersuite.CipherSuite, +) []ciphersuite.CipherSuite { if cert == nil || cert.PrivateKey == nil { return cipherSuites } @@ -487,7 +412,7 @@ func filterCipherSuitesForCertificate(cert *tls.Certificate, cipherSuites []Ciph certType = clientcertificate.RSASign } - filtered := []CipherSuite{} + filtered := []ciphersuite.CipherSuite{} for _, c := range cipherSuites { if c.AuthenticationType() != CipherSuiteAuthenticationTypeCertificate || certType == c.CertificateType() { filtered = append(filtered, c) diff --git a/cipher_suite_test.go b/cipher_suite_test.go index 9472b24dd..f7dfab3a0 100644 --- a/cipher_suite_test.go +++ b/cipher_suite_test.go @@ -125,7 +125,7 @@ func TestParseCipherSuitesForVersions(t *testing.T) { uint16(TLS_AES_128_GCM_SHA256), uint16(TLS_AES_256_GCM_SHA384), uint16(TLS_CHACHA20_POLY1305_SHA256), - }, cipherSuiteIDs(suites)) + }, configCipherSuiteIDs(suites)) }) t.Run("default dual stack", func(t *testing.T) { @@ -140,8 +140,8 @@ func TestParseCipherSuitesForVersions(t *testing.T) { require.NoError(t, err) require.Greater(t, len(suites), len(defaultCipherSuites13())) - assert.Equal(t, uint16(TLS_AES_128_GCM_SHA256), cipherSuiteIDs(suites)[0]) - assert.Contains(t, cipherSuiteIDs(suites), uint16(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)) + assert.Equal(t, uint16(TLS_AES_128_GCM_SHA256), configCipherSuiteIDs(suites)[0]) + assert.Contains(t, configCipherSuiteIDs(suites), uint16(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)) }) t.Run("selected suites are filtered by version", func(t *testing.T) { @@ -157,7 +157,7 @@ func TestParseCipherSuitesForVersions(t *testing.T) { protocol.Version1_2, ) require.NoError(t, err) - require.Equal(t, []uint16{uint16(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)}, cipherSuiteIDs(suites)) + require.Equal(t, []uint16{uint16(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)}, configCipherSuiteIDs(suites)) }) t.Run("selected suite must match version", func(t *testing.T) { @@ -182,7 +182,7 @@ func TestParseCipherSuitesForVersions(t *testing.T) { protocol.Version1_3, ) require.NoError(t, err) - require.Equal(t, []uint16{uint16(TLS_AES_128_GCM_SHA256)}, cipherSuiteIDs(suites)) + require.Equal(t, []uint16{uint16(TLS_AES_128_GCM_SHA256)}, configCipherSuiteIDs(suites)) }) t.Run("custom anonymous suites do not satisfy PSK configs", func(t *testing.T) { diff --git a/config.go b/config.go index 8dce180e9..71e25dd03 100644 --- a/config.go +++ b/config.go @@ -13,8 +13,6 @@ import ( "github.com/pion/dtls/v3/pkg/crypto/elliptic" ) -const keyLogLabelTLS12 = "CLIENT_RANDOM" - const defaultMTU = 1200 // bytes var defaultCurves = []elliptic.Curve{elliptic.X25519, elliptic.P256, elliptic.P384} //nolint:gochecknoglobals diff --git a/conn.go b/conn.go index dfe1e6cff..444416517 100644 --- a/conn.go +++ b/conn.go @@ -7,6 +7,7 @@ import ( "bytes" "context" "crypto/fips140" + "crypto/tls" "errors" "fmt" "io" @@ -16,7 +17,10 @@ import ( "time" "github.com/pion/dtls/v3/internal/closer" + dtlsconfig "github.com/pion/dtls/v3/internal/config" dtlserrors "github.com/pion/dtls/v3/internal/errors" + dtlsflight "github.com/pion/dtls/v3/internal/flight" + dtlsflight13 "github.com/pion/dtls/v3/internal/flight/flight13" dtlsstate "github.com/pion/dtls/v3/internal/state" "github.com/pion/dtls/v3/pkg/crypto/elliptic" "github.com/pion/dtls/v3/pkg/crypto/signaturehash" @@ -52,6 +56,15 @@ func invalidKeyingLabels() map[string]bool { } } +func toConfigCipherSuites(cipherSuites []CipherSuite) []dtlsconfig.CipherSuite { + out := make([]dtlsconfig.CipherSuite, 0, len(cipherSuites)) + for _, cipherSuite := range cipherSuites { + out = append(out, cipherSuite) + } + + return out +} + type addrPkt struct { rAddr net.Addr data []byte @@ -63,9 +76,10 @@ type recvHandshakeState struct { } type handshakeStart struct { - flight flightVal + flight12 dtlsflight.Flight12 + flight13 dtlsflight.Flight13 fsmState handshakeState - flights []*packet + flights []*dtlsflight.Packet transcript13 *handshakeTranscript13 postSetup func(context.Context) } @@ -75,7 +89,7 @@ type Conn struct { lock sync.RWMutex // Internal lock (must not be public) nextConn netctx.PacketConn // Embedded Conn, typically a udpconn we read/write from fragmentBuffer *fragmentBuffer // out-of-order and missing fragment handling - handshakeCache *handshakeCache // caching of handshake messages for verifyData generation + handshakeCache *dtlsflight.Cache // caching of handshake messages for verifyData generation decrypted chan any // Decrypted Application Data or error, pull by calling `Read` rAddr net.Addr state dtlsstate.State // Internal state @@ -206,43 +220,95 @@ func createConn( curves = filtered } + var customCipherSuites func() []dtlsconfig.CipherSuite + if config.customCipherSuites != nil { + customCipherSuites = func() []dtlsconfig.CipherSuite { + return toConfigCipherSuites(config.customCipherSuites()) + } + } + + var verifyConnection func(*dtlsstate.State) error + if config.verifyConnection != nil { + verifyConnection = func(state *dtlsstate.State) error { + stateSnapshot, err := generateState(state) + if err != nil { + return err + } + + return config.verifyConnection(stateSnapshot) + } + } + + var getCertificate func(*dtlsconfig.ClientHelloInfo) (*tls.Certificate, error) + if config.getCertificate != nil { + getCertificate = func(info *dtlsconfig.ClientHelloInfo) (*tls.Certificate, error) { + return config.getCertificate(&ClientHelloInfo{ + ServerName: info.ServerName, + CipherSuites: info.CipherSuites, + RandomBytes: info.RandomBytes, + }) + } + } + + var getClientCertificate func(*dtlsconfig.CertificateRequestInfo) (*tls.Certificate, error) + if config.getClientCertificate != nil { + getClientCertificate = func(info *dtlsconfig.CertificateRequestInfo) (*tls.Certificate, error) { + return config.getClientCertificate(&CertificateRequestInfo{AcceptableCAs: info.AcceptableCAs}) + } + } + + getSession := func(key []byte) ([]byte, []byte, error) { + session, err := config.sessionStore.Get(key) + + return session.ID, session.Secret, err + } + setSession := func(key, id, secret []byte) error { + return config.sessionStore.Set(key, Session{ID: id, Secret: secret}) + } + delSession := func(key []byte) error { + return config.sessionStore.Del(key) + } + handshakeConfig := &handshakeConfig{ - localPSKCallback: config.psk, - localPSKIdentityHint: config.PSKIdentityHint, - localCipherSuites: cipherSuites, - localSignatureSchemes: signatureSchemes, - localCertSignatureSchemes: certSignatureSchemes, - extendedMasterSecret: ExtendedMasterSecretType(config.ExtendedMasterSecret), - localSRTPProtectionProfiles: config.SRTPProtectionProfiles, - localSRTPMasterKeyIdentifier: config.SRTPMasterKeyIdentifier, - serverName: serverName, - supportedProtocols: config.SupportedProtocols, - clientAuth: ClientAuthType(config.ClientAuth), - localCertificates: config.Certificates, - insecureSkipVerify: config.InsecureSkipVerify, - verifyPeerCertificate: config.VerifyPeerCertificate, - verifyConnection: config.verifyConnection, - rootCAs: config.RootCAs, - clientCAs: config.ClientCAs, - customCipherSuites: config.customCipherSuites, - initialRetransmitInterval: workerInterval, - disableRetransmitBackoff: config.DisableRetransmitBackoff, - log: logger, - initialEpoch: 0, - keyLogWriter: config.KeyLogWriter, - sessionStore: config.sessionStore, - ellipticCurves: curves, - localGetCertificate: config.getCertificate, - localGetClientCertificate: config.getClientCertificate, - insecureSkipHelloVerify: config.InsecureSkipVerifyHello, - connectionIDGenerator: config.ConnectionIDGenerator, - helloRandomBytesGenerator: config.HelloRandomBytesGenerator, - clientHelloMessageHook: config.ClientHelloMessageHook, - serverHelloMessageHook: config.ServerHelloMessageHook, - certificateRequestMessageHook: config.CertificateRequestMessageHook, - resumeState: resumeState, - minVersion: minVersion, - maxVersion: maxVersion, + LocalPSKCallback: config.psk, + LocalPSKIdentityHint: config.PSKIdentityHint, + LocalCipherSuites: cipherSuites, + LocalSignatureSchemes: signatureSchemes, + LocalCertSignatureSchemes: certSignatureSchemes, + ExtendedMasterSecret: dtlsconfig.ExtendedMasterSecretType(config.ExtendedMasterSecret), + LocalSRTPProtectionProfiles: config.SRTPProtectionProfiles, + LocalSRTPMasterKeyIdentifier: config.SRTPMasterKeyIdentifier, + ServerName: serverName, + SupportedProtocols: config.SupportedProtocols, + ClientAuth: dtlsconfig.ClientAuthType(config.ClientAuth), + LocalCertificates: config.Certificates, + InsecureSkipVerify: config.InsecureSkipVerify, + VerifyPeerCertificate: config.VerifyPeerCertificate, + VerifyConnection: verifyConnection, + HasSessionStore: config.sessionStore != nil, + GetSession: getSession, + SetSession: setSession, + DelSession: delSession, + RootCAs: config.RootCAs, + ClientCAs: config.ClientCAs, + CustomCipherSuites: customCipherSuites, + InitialRetransmitInterval: workerInterval, + DisableRetransmitBackoff: config.DisableRetransmitBackoff, + Log: logger, + InitialEpoch: 0, + KeyLogWriter: config.KeyLogWriter, + EllipticCurves: curves, + LocalGetCertificate: getCertificate, + LocalGetClientCertificate: getClientCertificate, + InsecureSkipHelloVerify: config.InsecureSkipVerifyHello, + ConnectionIDGenerator: config.ConnectionIDGenerator, + HelloRandomBytesGenerator: config.HelloRandomBytesGenerator, + ClientHelloMessageHook: config.ClientHelloMessageHook, + ServerHelloMessageHook: config.ServerHelloMessageHook, + CertificateRequestMessageHook: config.CertificateRequestMessageHook, + ResumeState: resumeState, + MinVersion: minVersion, + MaxVersion: maxVersion, } conn := &Conn{ @@ -250,7 +316,7 @@ func createConn( nextConn: netctx.NewPacketConn(nextConn), handshakeConfig: handshakeConfig, fragmentBuffer: newFragmentBuffer(), - handshakeCache: newHandshakeCache(), + handshakeCache: dtlsflight.NewCache(), maximumTransmissionUnit: mtu, paddingLengthGenerator: paddingLengthGenerator, @@ -319,11 +385,14 @@ func (c *Conn) HandshakeContext(ctx context.Context) error { //nolint:cyclop // In addition, the hash and signature algorithms MUST be compatible // with the key in the server's end-entity certificate. if !c.state.IsClient { - cert, err := c.handshakeConfig.getCertificate(&ClientHelloInfo{}) + cert, err := c.handshakeConfig.GetCertificate(&dtlsconfig.ClientHelloInfo{}) if err != nil && !errors.Is(err, dtlserrors.ErrNoCertificates) { return err } - c.handshakeConfig.localCipherSuites = filterCipherSuitesForCertificate(cert, c.handshakeConfig.localCipherSuites) + c.handshakeConfig.LocalCipherSuites = filterCipherSuitesForCertificate( + cert, + c.handshakeConfig.LocalCipherSuites, + ) } start, err := c.prepareHandshakeStart(ctx) @@ -331,11 +400,11 @@ func (c *Conn) HandshakeContext(ctx context.Context) error { //nolint:cyclop return err } - c.handshakeConfig.localCipherSuites = filterCipherSuitesForVersion( - c.handshakeConfig.localCipherSuites, + c.handshakeConfig.LocalCipherSuites = filterCipherSuitesForVersion( + c.handshakeConfig.LocalCipherSuites, c.state.LocalVersion, ) - if len(c.handshakeConfig.localCipherSuites) == 0 { + if len(c.handshakeConfig.LocalCipherSuites) == 0 { return dtlserrors.ErrNoAvailableCipherSuites } @@ -365,36 +434,36 @@ func (c *Conn) HandshakeContext(ctx context.Context) error { //nolint:cyclop func (c *Conn) prepareHandshakeStart(ctx context.Context) (handshakeStart, error) { switch { // DTLS 1.2 only - case c.state.IsClient && c.handshakeConfig.maxVersion == protocol.Version1_2: + case c.state.IsClient && c.handshakeConfig.MaxVersion == protocol.Version1_2: c.state.LocalVersion = protocol.Version1_2 - if c.handshakeConfig.resumeState != nil { - c.state = *c.handshakeConfig.resumeState + if c.handshakeConfig.ResumeState != nil { + c.state = *c.handshakeConfig.ResumeState c.state.LocalVersion = protocol.Version1_2 - return handshakeStart{flight: flight5, fsmState: handshakeFinished}, nil + return handshakeStart{flight12: dtlsflight.Flight5, fsmState: handshakeFinished}, nil } - return handshakeStart{flight: flight1, fsmState: handshakePreparing}, nil - case !c.state.IsClient && c.handshakeConfig.maxVersion == protocol.Version1_2: + return handshakeStart{flight12: dtlsflight.Flight1, fsmState: handshakePreparing}, nil + case !c.state.IsClient && c.handshakeConfig.MaxVersion == protocol.Version1_2: c.state.LocalVersion = protocol.Version1_2 - if c.handshakeConfig.resumeState != nil { - c.state = *c.handshakeConfig.resumeState + if c.handshakeConfig.ResumeState != nil { + c.state = *c.handshakeConfig.ResumeState c.state.LocalVersion = protocol.Version1_2 - return handshakeStart{flight: flight6, fsmState: handshakeFinished}, nil + return handshakeStart{flight12: dtlsflight.Flight6, fsmState: handshakeFinished}, nil } - return handshakeStart{flight: flight0, fsmState: handshakePreparing}, nil + return handshakeStart{flight12: dtlsflight.Flight0, fsmState: handshakePreparing}, nil // DTLS 1.3 only - case c.state.IsClient && c.handshakeConfig.minVersion == protocol.Version1_3: + case c.state.IsClient && c.handshakeConfig.MinVersion == protocol.Version1_3: c.state.LocalVersion = protocol.Version1_3 - return handshakeStart{flight: flightVal(flight13_1), fsmState: handshakePreparing}, nil - case !c.state.IsClient && c.handshakeConfig.minVersion == protocol.Version1_3: + return handshakeStart{flight13: dtlsflight.Flight13_1, fsmState: handshakePreparing}, nil + case !c.state.IsClient && c.handshakeConfig.MinVersion == protocol.Version1_3: c.state.LocalVersion = protocol.Version1_3 - return handshakeStart{flight: flightVal(flight13_0), fsmState: handshakePreparing}, nil + return handshakeStart{flight13: dtlsflight.Flight13_0, fsmState: handshakePreparing}, nil // Dual-stack // This mode sends or read handshake messages to decide version without starting a FSM @@ -409,7 +478,8 @@ func (c *Conn) prepareHandshakeStart(ctx context.Context) (handshakeStart, error } return handshakeStart{ - flight: flightVal(flight13_1), + flight12: dtlsflight.Flight1, + flight13: dtlsflight.Flight13_1, fsmState: handshakeWaiting, flights: initialFlights, transcript13: initialTranscript13, @@ -421,7 +491,11 @@ func (c *Conn) prepareHandshakeStart(ctx context.Context) (handshakeStart, error return handshakeStart{}, err } - return handshakeStart{flight: flightVal(flight13_0), fsmState: handshakePreparing}, nil + return handshakeStart{ + flight12: dtlsflight.Flight0, + flight13: dtlsflight.Flight13_0, + fsmState: handshakePreparing, + }, nil } } @@ -560,9 +634,9 @@ func (c *Conn) Write(payload []byte) (int, error) { //nolint:godox // TODO: check for version - return len(payload), c.writePackets(c.writeDeadline, []*packet{ + return len(payload), c.writePackets(c.writeDeadline, []*dtlsflight.Packet{ { - record: &recordlayer.RecordLayer{ + Record: &recordlayer.RecordLayer{ Header: recordlayer.Header{ Epoch: c.state.GetLocalEpoch(), Version: protocol.Version1_2, @@ -571,8 +645,8 @@ func (c *Conn) Write(payload []byte) (int, error) { Data: payload, }, }, - shouldWrapCID: len(c.state.RemoteConnectionID) > 0, - shouldEncrypt: true, + ShouldWrapCID: len(c.state.RemoteConnectionID) > 0, + ShouldEncrypt: true, }, }) } @@ -622,26 +696,26 @@ func (c *Conn) RemoteSRTPMasterKeyIdentifier() ([]byte, bool) { return c.state.RemoteSRTPMasterKeyIdentifier, true } -func (c *Conn) writePackets(ctx context.Context, pkts []*packet) error { +func (c *Conn) writePackets(ctx context.Context, pkts []*dtlsflight.Packet) error { c.lock.Lock() defer c.lock.Unlock() var rawPackets [][]byte for _, pkt := range pkts { - if dtlsHandshake, ok := pkt.record.Content.(*handshake.Handshake); ok { - handshakeRaw, err := pkt.record.Marshal() + if dtlsHandshake, ok := pkt.Record.Content.(*handshake.Handshake); ok { + handshakeRaw, err := pkt.Record.Marshal() if err != nil { return err } c.log.Tracef("[handshake:%v] -> %s (epoch: %d, seq: %d)", srvCliStr(c.state.IsClient), dtlsHandshake.Header.Type.String(), - pkt.record.Header.Epoch, dtlsHandshake.Header.MessageSequence) + pkt.Record.Header.Epoch, dtlsHandshake.Header.MessageSequence) - c.handshakeCache.push( + c.handshakeCache.Push( handshakeRaw[recordlayer.FixedHeaderSize:], - pkt.record.Header.Epoch, + pkt.Record.Header.Epoch, dtlsHandshake.Header.MessageSequence, dtlsHandshake.Header.Type, c.state.IsClient, @@ -696,8 +770,8 @@ func (c *Conn) compactRawPackets(rawPackets [][]byte) [][]byte { return combinedRawPackets } -func (c *Conn) processPacket(pkt *packet) ([]byte, error) { //nolint:cyclop - epoch := pkt.record.Header.Epoch +func (c *Conn) processPacket(pkt *dtlsflight.Packet) ([]byte, error) { //nolint:cyclop + epoch := pkt.Record.Header.Epoch for len(c.state.LocalSequenceNumber) <= int(epoch) { c.state.LocalSequenceNumber = append(c.state.LocalSequenceNumber, uint64(0)) } @@ -708,51 +782,51 @@ func (c *Conn) processPacket(pkt *packet) ([]byte, error) { //nolint:cyclop // prior to allowing the sequence number to wrap. return nil, dtlserrors.ErrSequenceNumberOverflow } - pkt.record.Header.SequenceNumber = seq + pkt.Record.Header.SequenceNumber = seq var rawPacket []byte - if pkt.shouldWrapCID { //nolint:nestif + if pkt.ShouldWrapCID { //nolint:nestif // Record must be marshaled to populate fields used in inner plaintext. - if _, err := pkt.record.Marshal(); err != nil { + if _, err := pkt.Record.Marshal(); err != nil { return nil, err } - content, err := pkt.record.Content.Marshal() + content, err := pkt.Record.Content.Marshal() if err != nil { return nil, err } inner := &recordlayer.InnerPlaintext{ Content: content, - RealType: pkt.record.Header.ContentType, + RealType: pkt.Record.Header.ContentType, } rawInner, err := inner.Marshal() //nolint:govet if err != nil { return nil, err } cidHeader := &recordlayer.Header{ - Version: pkt.record.Header.Version, + Version: pkt.Record.Header.Version, ContentType: protocol.ContentTypeConnectionID, - Epoch: pkt.record.Header.Epoch, + Epoch: pkt.Record.Header.Epoch, ContentLen: uint16(len(rawInner)), //nolint:gosec //G115 ConnectionID: c.state.RemoteConnectionID, - SequenceNumber: pkt.record.Header.SequenceNumber, + SequenceNumber: pkt.Record.Header.SequenceNumber, } rawPacket, err = cidHeader.Marshal() if err != nil { return nil, err } - pkt.record.Header = *cidHeader + pkt.Record.Header = *cidHeader rawPacket = append(rawPacket, rawInner...) } else { var err error - rawPacket, err = pkt.record.Marshal() + rawPacket, err = pkt.Record.Marshal() if err != nil { return nil, err } } - if pkt.shouldEncrypt { + if pkt.ShouldEncrypt { var err error - rawPacket, err = c.state.CipherSuite.Encrypt(pkt.record, rawPacket) + rawPacket, err = c.state.CipherSuite.Encrypt(pkt.Record, rawPacket) if err != nil { return nil, err } @@ -762,14 +836,14 @@ func (c *Conn) processPacket(pkt *packet) ([]byte, error) { //nolint:cyclop } //nolint:cyclop -func (c *Conn) processHandshakePacket(pkt *packet, dtlsHandshake *handshake.Handshake) ([][]byte, error) { +func (c *Conn) processHandshakePacket(pkt *dtlsflight.Packet, dtlsHandshake *handshake.Handshake) ([][]byte, error) { rawPackets := make([][]byte, 0) handshakeFragments, err := c.fragmentHandshake(dtlsHandshake) if err != nil { return nil, err } - epoch := pkt.record.Header.Epoch + epoch := pkt.Record.Header.Epoch for len(c.state.LocalSequenceNumber) <= int(epoch) { c.state.LocalSequenceNumber = append(c.state.LocalSequenceNumber, uint64(0)) } @@ -781,7 +855,7 @@ func (c *Conn) processHandshakePacket(pkt *packet, dtlsHandshake *handshake.Hand } var rawPacket []byte - if pkt.shouldWrapCID { + if pkt.ShouldWrapCID { inner := &recordlayer.InnerPlaintext{ Content: handshakeFragment, RealType: protocol.ContentTypeHandshake, @@ -792,25 +866,25 @@ func (c *Conn) processHandshakePacket(pkt *packet, dtlsHandshake *handshake.Hand return nil, err } cidHeader := &recordlayer.Header{ - Version: pkt.record.Header.Version, + Version: pkt.Record.Header.Version, ContentType: protocol.ContentTypeConnectionID, - Epoch: pkt.record.Header.Epoch, + Epoch: pkt.Record.Header.Epoch, ContentLen: uint16(len(rawInner)), //nolint:gosec //G115 ConnectionID: c.state.RemoteConnectionID, - SequenceNumber: pkt.record.Header.SequenceNumber, + SequenceNumber: pkt.Record.Header.SequenceNumber, } rawPacket, err = cidHeader.Marshal() if err != nil { return nil, err } - pkt.record.Header = *cidHeader + pkt.Record.Header = *cidHeader rawPacket = append(rawPacket, rawInner...) } else { recordlayerHeader := &recordlayer.Header{ - Version: pkt.record.Header.Version, - ContentType: pkt.record.Header.ContentType, + Version: pkt.Record.Header.Version, + ContentType: pkt.Record.Header.ContentType, ContentLen: uint16(len(handshakeFragment)), //nolint:gosec // G115 - Epoch: pkt.record.Header.Epoch, + Epoch: pkt.Record.Header.Epoch, SequenceNumber: seq, } @@ -819,13 +893,13 @@ func (c *Conn) processHandshakePacket(pkt *packet, dtlsHandshake *handshake.Hand return nil, err } - pkt.record.Header = *recordlayerHeader + pkt.Record.Header = *recordlayerHeader rawPacket = append(rawPacket, handshakeFragment...) } - if pkt.shouldEncrypt { + if pkt.ShouldEncrypt { var err error - rawPacket, err = c.state.CipherSuite.Encrypt(pkt.record, rawPacket) + rawPacket, err = c.state.CipherSuite.Encrypt(pkt.Record, rawPacket) if err != nil { return nil, err } @@ -1141,7 +1215,7 @@ func (c *Conn) handleIncomingPacket( continue } - c.handshakeCache.push(out, epoch, header.MessageSequence, header.Type, !c.state.IsClient) + c.handshakeCache.Push(out, epoch, header.MessageSequence, header.Type, !c.state.IsClient) } return true, isRetransmit, nil, nil @@ -1226,9 +1300,10 @@ func (c *Conn) notify(ctx context.Context, level alert.Level, desc alert.Descrip if c.state.LocalVersion == protocol.Version1_2 { // According to the RFC, we need to delete the stored session. // https://datatracker.ietf.org/doc/html/rfc5246#section-7.2 - if ss := c.fsm.(*handshakeFSM12).cfg.sessionStore; ss != nil { //nolint:forcetypeassert + cfg := c.fsm.(*handshakeFSM12).cfg //nolint:forcetypeassert + if cfg.HasSessionStore { c.log.Tracef("clean invalid session: %s", c.state.SessionID) - if err := ss.Del(c.sessionKey()); err != nil { + if err := cfg.DelSession(c.sessionKey()); err != nil { return err } } @@ -1236,9 +1311,9 @@ func (c *Conn) notify(ctx context.Context, level alert.Level, desc alert.Descrip } // This should be updated with DTLS 1.3 record encoding. - return c.writePackets(ctx, []*packet{ + return c.writePackets(ctx, []*dtlsflight.Packet{ { - record: &recordlayer.RecordLayer{ + Record: &recordlayer.RecordLayer{ Header: recordlayer.Header{ Epoch: c.state.GetLocalEpoch(), Version: protocol.Version1_2, @@ -1248,8 +1323,8 @@ func (c *Conn) notify(ctx context.Context, level alert.Level, desc alert.Descrip Description: desc, }, }, - shouldWrapCID: len(c.state.RemoteConnectionID) > 0, - shouldEncrypt: c.isHandshakeCompletedSuccessfully(), + ShouldWrapCID: len(c.state.RemoteConnectionID) > 0, + ShouldEncrypt: c.isHandshakeCompletedSuccessfully(), }, }) } @@ -1277,14 +1352,13 @@ func (c *Conn) negotiateVersionServer(ctx context.Context) error { } //nolint:cyclop -func (c *Conn) negotiateVersionClient(ctx context.Context) ([]*packet, *handshakeTranscript13, error) { +func (c *Conn) negotiateVersionClient(ctx context.Context) ([]*dtlsflight.Packet, *handshakeTranscript13, error) { transcript := newHandshakeTranscript13() - pkts, dtlsAlert, err := flight13_1Generate(c, &handshakeContext13{ - state: &c.state, - cache: c.handshakeCache, - cfg: c.handshakeConfig, - transcript: transcript, - }) + gen, _, ok := dtlsflight13.GetGenerator(dtlsflight.Flight13_1) + if !ok { + return nil, nil, dtlserrors.ErrFlightUnimplemented13 + } + pkts, dtlsAlert, err := gen(adaptFlightConn(c), &c.state, c.handshakeCache, c.handshakeConfig) if dtlsAlert != nil { if alertErr := c.notify(ctx, dtlsAlert.Level, dtlsAlert.Description); alertErr != nil && err == nil { err = alertErr @@ -1321,8 +1395,8 @@ func (c *Conn) negotiateVersionClient(ctx context.Context) ([]*packet, *handshak // ClientHello and, if found, sets localVersion and remoteVersions. // Returns true once the version can be decided. func (c *Conn) pickVersionFromClientHello() (bool, error) { - _, msgs, ok := c.handshakeCache.fullPullMap(0, c.state.CipherSuite, - handshakeCachePullRule{handshake.TypeClientHello, c.handshakeConfig.initialEpoch, true, false}, + _, msgs, ok := c.handshakeCache.FullPullMap(0, c.state.CipherSuite, + dtlsflight.HandshakeCachePullRule{Typ: handshake.TypeClientHello, Epoch: c.handshakeConfig.InitialEpoch, IsClient: true, Optional: false}, //nolint:lll ) if !ok { return false, nil @@ -1346,7 +1420,7 @@ func (c *Conn) pickVersionFromClientHello() (bool, error) { remote = []protocol.Version{ch.Version} } - chosen, ok := selectVersion(remote, c.handshakeConfig.minVersion, c.handshakeConfig.maxVersion) + chosen, ok := selectVersion(remote, c.handshakeConfig.MinVersion, c.handshakeConfig.MaxVersion) if !ok { return false, dtlserrors.ErrNoCommonProtocolVersion } @@ -1401,8 +1475,8 @@ func (c *Conn) pickVersionFromHelloVerifyRequest(hvr *handshake.MessageHelloVeri } func remoteVersionsFromServerHello(sh *handshake.MessageServerHello) ([]protocol.Version, error) { - remote, seenSupportedVersions, err := serverHelloSelectedVersions(sh.Extensions) - if isHelloRetryRequest(sh) { + remote, seenSupportedVersions, err := dtlsflight13.ServerHelloSelectedVersions(sh.Extensions) + if dtlsflight13.IsHelloRetryRequest(sh) { return remoteVersionsFromHelloRetryRequest(remote, seenSupportedVersions, err) } if err != nil { @@ -1431,7 +1505,7 @@ func remoteVersionsFromHelloRetryRequest( } func (c *Conn) selectRemoteVersion(remote []protocol.Version) error { - chosen, ok := selectVersion(remote, c.handshakeConfig.minVersion, c.handshakeConfig.maxVersion) + chosen, ok := selectVersion(remote, c.handshakeConfig.MinVersion, c.handshakeConfig.MaxVersion) if !ok { return dtlserrors.ErrNoCommonProtocolVersion } @@ -1443,26 +1517,26 @@ func (c *Conn) selectRemoteVersion(remote []protocol.Version) error { // findCachedServerMessage pulls the most recent handshake message of the // given type sent by the peer from the cache, if any. -func (c *Conn) findCachedServerMessage(t handshake.Type) handshake.Message { - _, msgs, ok := c.handshakeCache.fullPullMap(0, c.state.CipherSuite, - handshakeCachePullRule{t, c.handshakeConfig.initialEpoch, false, true}, +func (c *Conn) findCachedServerMessage(messageType handshake.Type) handshake.Message { + _, msgs, ok := c.handshakeCache.FullPullMap(0, c.state.CipherSuite, + dtlsflight.HandshakeCachePullRule{Typ: messageType, Epoch: c.handshakeConfig.InitialEpoch, IsClient: false, Optional: true}, //nolint:lll ) if !ok { return nil } - return msgs[t] + return msgs[messageType] } // stampHandshakeSequence assigns the DTLS message_sequence to each handshake // record in pkts, using and advancing state.handshakeSendSequence. This is // the subset of handshakeFSM.prepare()'s bookkeeping that generated dual-stack // packets need before being passed to writePackets. -func (c *Conn) stampHandshakeSequence(pkts []*packet) { - epoch := c.handshakeConfig.initialEpoch +func (c *Conn) stampHandshakeSequence(pkts []*dtlsflight.Packet) { + epoch := c.handshakeConfig.InitialEpoch for _, p := range pkts { - p.record.Header.Epoch += epoch - if h, ok := p.record.Content.(*handshake.Handshake); ok { + p.Record.Header.Epoch += epoch + if h, ok := p.Record.Content.(*handshake.Handshake); ok { h.Header.MessageSequence = uint16(c.state.HandshakeSendSequence) //nolint:gosec // G115 c.state.HandshakeSendSequence++ } @@ -1545,7 +1619,7 @@ func (c *Conn) handshake(ctx context.Context, start handshakeStart) error { &c.state, c.handshakeCache, c.handshakeConfig, - flightVal13(start.flight), + start.flight13, start.flights, start.transcript13, ) @@ -1553,27 +1627,27 @@ func (c *Conn) handshake(ctx context.Context, start handshakeStart) error { return err } c.fsm = fsm - c.handshakeConfig.onFlightState13 = func(_ flightVal13, s handshakeState) { + c.handshakeConfig.OnFlightState13 = func(_ uint8, s uint8) { // The ACK for the last flights has been received and we are in a Finished state. // nolint:godox // TODO: should be moved to FSM. - if s == handshakeFinished && c.setHandshakeCompletedSuccessfully() { + if handshakeState(s) == handshakeFinished && c.setHandshakeCompletedSuccessfully() { close(done) } } } else { c.fsm = &handshakeFSM12{ - currentFlight: start.flight, + currentFlight: start.flight12, flights: start.flights, retransmit: start.flights != nil, state: &c.state, cache: c.handshakeCache, cfg: c.handshakeConfig, - retransmitInterval: c.handshakeConfig.initialRetransmitInterval, + retransmitInterval: c.handshakeConfig.InitialRetransmitInterval, closed: make(chan struct{}), } - c.handshakeConfig.onFlightState = func(_ flightVal, s handshakeState) { - if s == handshakeFinished && c.setHandshakeCompletedSuccessfully() { + c.handshakeConfig.OnFlightState = func(_ uint8, s uint8) { + if handshakeState(s) == handshakeFinished && c.setHandshakeCompletedSuccessfully() { close(done) } } @@ -1782,10 +1856,10 @@ func (c *Conn) sessionKey() []byte { // delimiter character which is not allowed to be in // neither address or domain name. if c.state.LocalVersion == protocol.Version1_3 { - return []byte(c.rAddr.String() + "_" + c.fsm.(*handshakeFSM13).cfg.serverName) //nolint:forcetypeassert + return []byte(c.rAddr.String() + "_" + c.fsm.(*handshakeFSM13).cfg.ServerName) //nolint:forcetypeassert } - return []byte(c.rAddr.String() + "_" + c.fsm.(*handshakeFSM12).cfg.serverName) //nolint:forcetypeassert + return []byte(c.rAddr.String() + "_" + c.fsm.(*handshakeFSM12).cfg.ServerName) //nolint:forcetypeassert } return c.state.SessionID diff --git a/conn_test.go b/conn_test.go index c6dda23a2..cc44efffe 100644 --- a/conn_test.go +++ b/conn_test.go @@ -26,6 +26,7 @@ import ( "github.com/pion/dtls/v3/internal/ciphersuite" dtlserrors "github.com/pion/dtls/v3/internal/errors" + dtlsflight "github.com/pion/dtls/v3/internal/flight" dtlsstate "github.com/pion/dtls/v3/internal/state" "github.com/pion/dtls/v3/pkg/crypto/elliptic" "github.com/pion/dtls/v3/pkg/crypto/hash" @@ -172,9 +173,9 @@ func TestSequenceNumberOverflow(t *testing.T) { atomic.StoreUint64(&ca.state.LocalSequenceNumber[0], recordlayer.MaxSequenceNumber+1) // Try to send handshake packet. - werr := ca.writePackets(ctx, []*packet{ + werr := ca.writePackets(ctx, []*dtlsflight.Packet{ { - record: &recordlayer.RecordLayer{ + Record: &recordlayer.RecordLayer{ Header: recordlayer.Header{ Version: protocol.Version1_2, }, @@ -3122,7 +3123,7 @@ func TestEllipticCurveConfiguration(t *testing.T) { assert.Equal( t, len(test.HandshakeCurves), - len(server.fsm.(*handshakeFSM12).cfg.ellipticCurves), //nolint:forcetypeassert + len(server.fsm.(*handshakeFSM12).cfg.EllipticCurves), //nolint:forcetypeassert "Failed to configure Elliptic curves", ) @@ -3130,7 +3131,7 @@ func TestEllipticCurveConfiguration(t *testing.T) { assert.Equal( t, c, - server.fsm.(*handshakeFSM12).cfg.ellipticCurves[i], //nolint:forcetypeassert + server.fsm.(*handshakeFSM12).cfg.EllipticCurves[i], //nolint:forcetypeassert "Failed to maintain Elliptic curve order", ) } diff --git a/crypto_test.go b/crypto_test.go index e44574b51..98eadea6b 100644 --- a/crypto_test.go +++ b/crypto_test.go @@ -11,6 +11,7 @@ import ( "testing" dtlserrors "github.com/pion/dtls/v3/internal/errors" + dtlscrypto "github.com/pion/dtls/v3/internal/handshakecrypto" "github.com/pion/dtls/v3/pkg/crypto/elliptic" "github.com/pion/dtls/v3/pkg/crypto/hash" "github.com/pion/dtls/v3/pkg/crypto/signature" @@ -120,7 +121,7 @@ func TestGenerateKeySignature(t *testing.T) { 0x5c, 0x36, 0x75, 0x86, } - signature, err := generateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, + signature, err := dtlscrypto.GenerateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, key, hash.SHA256, signature.RSA) assert.NoError(t, err) assert.Equal(t, expectedSignature, signature) @@ -137,13 +138,13 @@ func TestRSAPSSSignatureGeneration(t *testing.T) { assert.NoError(t, err) // Generate PSS signature - sig, err := generateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, + sig, err := dtlscrypto.GenerateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, key, hash.SHA256, signature.RSA_PSS_RSAE_SHA256) assert.NoError(t, err) assert.NotNil(t, sig) // Verify that PSS signature is different from PKCS#1 v1.5 (PSS is randomized) - sig2, err := generateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, + sig2, err := dtlscrypto.GenerateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, key, hash.SHA256, signature.RSA_PSS_RSAE_SHA256) assert.NoError(t, err) // PSS signatures should be different each time due to random salt @@ -169,17 +170,17 @@ func TestRSAPSSSignatureVerification(t *testing.T) { assert.NoError(t, err) // Generate PSS signature - sig, err := generateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, + sig, err := dtlscrypto.GenerateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, key, hash.SHA256, signature.RSA_PSS_RSAE_SHA256) assert.NoError(t, err) // Verify PSS signature - expectedMsg := valueKeyMessage(clientRandom, serverRandom, publicKey, elliptic.X25519) - err = verifyKeySignature(expectedMsg, sig, hash.SHA256, signature.RSA_PSS_RSAE_SHA256, [][]byte{rawCert}) + expectedMsg := dtlscrypto.ValueKeyMessage(clientRandom, serverRandom, publicKey, elliptic.X25519) + err = dtlscrypto.VerifyKeySignature(expectedMsg, sig, hash.SHA256, signature.RSA_PSS_RSAE_SHA256, [][]byte{rawCert}) assert.NoError(t, err) // Verify that PKCS#1 v1.5 verification fails for PSS signature - err = verifyKeySignature(expectedMsg, sig, hash.SHA256, signature.RSA, [][]byte{rawCert}) + err = dtlscrypto.VerifyKeySignature(expectedMsg, sig, hash.SHA256, signature.RSA, [][]byte{rawCert}) assert.Error(t, err) } @@ -201,27 +202,29 @@ func TestRSAPSSVsPKCS1v15(t *testing.T) { rawCert, err := x509.CreateCertificate(rand.Reader, cert, cert, &key.PublicKey, key) assert.NoError(t, err) - expectedMsg := valueKeyMessage(clientRandom, serverRandom, publicKey, elliptic.X25519) + expectedMsg := dtlscrypto.ValueKeyMessage(clientRandom, serverRandom, publicKey, elliptic.X25519) // Generate and verify PKCS#1 v1.5 signature - pkcs1Sig, err := generateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, + pkcs1Sig, err := dtlscrypto.GenerateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, key, hash.SHA256, signature.RSA) assert.NoError(t, err) - err = verifyKeySignature(expectedMsg, pkcs1Sig, hash.SHA256, signature.RSA, [][]byte{rawCert}) + err = dtlscrypto.VerifyKeySignature(expectedMsg, pkcs1Sig, hash.SHA256, signature.RSA, [][]byte{rawCert}) assert.NoError(t, err) // Generate and verify PSS signature - pssSig, err := generateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, + pssSig, err := dtlscrypto.GenerateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, key, hash.SHA256, signature.RSA_PSS_RSAE_SHA256) assert.NoError(t, err) - err = verifyKeySignature(expectedMsg, pssSig, hash.SHA256, signature.RSA_PSS_RSAE_SHA256, [][]byte{rawCert}) + err = dtlscrypto.VerifyKeySignature(expectedMsg, pssSig, hash.SHA256, signature.RSA_PSS_RSAE_SHA256, [][]byte{rawCert}) assert.NoError(t, err) // Verify cross-verification fails - err = verifyKeySignature(expectedMsg, pkcs1Sig, hash.SHA256, signature.RSA_PSS_RSAE_SHA256, [][]byte{rawCert}) + err = dtlscrypto.VerifyKeySignature( + expectedMsg, pkcs1Sig, hash.SHA256, signature.RSA_PSS_RSAE_SHA256, [][]byte{rawCert}, + ) assert.Error(t, err, "PKCS#1 v1.5 signature should not verify as PSS") - err = verifyKeySignature(expectedMsg, pssSig, hash.SHA256, signature.RSA, [][]byte{rawCert}) + err = dtlscrypto.VerifyKeySignature(expectedMsg, pssSig, hash.SHA256, signature.RSA, [][]byte{rawCert}) assert.Error(t, err, "PSS signature should not verify as PKCS#1 v1.5") } @@ -243,7 +246,7 @@ func TestRSAPSSRSAEVariants(t *testing.T) { rawCert, err := x509.CreateCertificate(rand.Reader, cert, cert, &key.PublicKey, key) assert.NoError(t, err) - expectedMsg := valueKeyMessage(clientRandom, serverRandom, publicKey, elliptic.X25519) + expectedMsg := dtlscrypto.ValueKeyMessage(clientRandom, serverRandom, publicKey, elliptic.X25519) // Test RSA-PSS RSAE variants (work with standard RSA certs) // Note: We don't test RSA_PSS_PSS variants here because they require id-RSASSA-PSS OID certs, @@ -262,14 +265,14 @@ func TestRSAPSSRSAEVariants(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // Generate signature - sig, err := generateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, + sig, err := dtlscrypto.GenerateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, key, tc.hashAlgo, tc.sigAlgo) assert.NoError(t, err) assert.NotNil(t, sig) assert.True(t, len(sig) > 0, "Signature should not be empty") // Verify signature - err = verifyKeySignature(expectedMsg, sig, tc.hashAlgo, tc.sigAlgo, [][]byte{rawCert}) + err = dtlscrypto.VerifyKeySignature(expectedMsg, sig, tc.hashAlgo, tc.sigAlgo, [][]byte{rawCert}) assert.NoError(t, err, "Signature verification should succeed") // Verify IsPSS() returns true @@ -307,16 +310,16 @@ func TestCertificateOIDValidation(t *testing.T) { pssCertBlock, _ := pem.Decode([]byte(rsaPSSCertificate)) pssCertBytes := pssCertBlock.Bytes - expectedMsg := valueKeyMessage(clientRandom, serverRandom, publicKey, elliptic.X25519) + expectedMsg := dtlscrypto.ValueKeyMessage(clientRandom, serverRandom, publicKey, elliptic.X25519) t.Run("RSAE_with_rsaEncryption_OID_succeeds", func(t *testing.T) { // Generate signature with RSAE algorithm using rsaEncryption cert - sig, err := generateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, + sig, err := dtlscrypto.GenerateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, rsaKey, hash.SHA256, signature.RSA_PSS_RSAE_SHA256) assert.NoError(t, err) // Should succeed: RSAE + rsaEncryption OID is valid per RFC 8446 - err = verifyKeySignature( + err = dtlscrypto.VerifyKeySignature( expectedMsg, sig, hash.SHA256, signature.RSA_PSS_RSAE_SHA256, [][]byte{rsaEncryptionCertBytes}, ) assert.NoError(t, err) @@ -331,12 +334,12 @@ func TestCertificateOIDValidation(t *testing.T) { t.Run("PSS_with_rsaEncryption_OID_fails", func(t *testing.T) { // Generate signature with PSS algorithm - sig, err := generateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, + sig, err := dtlscrypto.GenerateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, rsaKey, hash.SHA256, signature.RSA_PSS_PSS_SHA256) assert.NoError(t, err) // Should fail: PSS algorithm requires id-RSASSA-PSS OID, not rsaEncryption - err = verifyKeySignature( + err = dtlscrypto.VerifyKeySignature( expectedMsg, sig, hash.SHA256, signature.RSA_PSS_PSS_SHA256, [][]byte{rsaEncryptionCertBytes}, ) assert.Error(t, err) @@ -345,12 +348,14 @@ func TestCertificateOIDValidation(t *testing.T) { t.Run("RSAE_with_idRSASSAPSS_OID_fails", func(t *testing.T) { // Generate signature with RSAE algorithm - sig, err := generateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, + sig, err := dtlscrypto.GenerateKeySignature(clientRandom, serverRandom, publicKey, elliptic.X25519, rsaKey, hash.SHA256, signature.RSA_PSS_RSAE_SHA256) assert.NoError(t, err) // Should fail: RSAE algorithm requires rsaEncryption OID, not id-RSASSA-PSS - err = verifyKeySignature(expectedMsg, sig, hash.SHA256, signature.RSA_PSS_RSAE_SHA256, [][]byte{pssCertBytes}) + err = dtlscrypto.VerifyKeySignature( + expectedMsg, sig, hash.SHA256, signature.RSA_PSS_RSAE_SHA256, [][]byte{pssCertBytes}, + ) assert.Error(t, err) assert.ErrorIs(t, err, dtlserrors.ErrInvalidCertificateOID) }) @@ -370,7 +375,7 @@ func TestValidateCertificateSignatureAlgorithms(t *testing.T) { certs := []*x509.Certificate{ createTestCert(x509.SHA256WithRSA, false), } - err := validateCertificateSignatureAlgorithms(certs, nil) + err := dtlscrypto.ValidateCertificateSignatureAlgorithms(certs, nil) assert.NoError(t, err) }) @@ -382,7 +387,7 @@ func TestValidateCertificateSignatureAlgorithms(t *testing.T) { allowed := []signaturehash.Algorithm{ {Hash: hash.SHA256, Signature: signature.RSA}, } - err := validateCertificateSignatureAlgorithms(certs, allowed) + err := dtlscrypto.ValidateCertificateSignatureAlgorithms(certs, allowed) assert.NoError(t, err) }) @@ -394,7 +399,7 @@ func TestValidateCertificateSignatureAlgorithms(t *testing.T) { allowed := []signaturehash.Algorithm{ {Hash: hash.SHA384, Signature: signature.ECDSA}, // Different algorithm } - err := validateCertificateSignatureAlgorithms(certs, allowed) + err := dtlscrypto.ValidateCertificateSignatureAlgorithms(certs, allowed) assert.ErrorIs(t, err, dtlserrors.ErrInvalidCertificateSignatureAlgorithm) }) @@ -407,7 +412,7 @@ func TestValidateCertificateSignatureAlgorithms(t *testing.T) { {Hash: hash.SHA256, Signature: signature.RSA}, // Only allows SHA256 } // Should pass because root (SHA384) is not validated - err := validateCertificateSignatureAlgorithms(certs, allowed) + err := dtlscrypto.ValidateCertificateSignatureAlgorithms(certs, allowed) assert.NoError(t, err) }) @@ -422,7 +427,7 @@ func TestValidateCertificateSignatureAlgorithms(t *testing.T) { {Hash: hash.SHA384, Signature: signature.RSA}, // SHA512 not needed since root is not validated } - err := validateCertificateSignatureAlgorithms(certs, allowed) + err := dtlscrypto.ValidateCertificateSignatureAlgorithms(certs, allowed) assert.NoError(t, err) }) @@ -435,7 +440,7 @@ func TestValidateCertificateSignatureAlgorithms(t *testing.T) { allowed := []signaturehash.Algorithm{ {Hash: hash.SHA256, Signature: signature.RSA}, // Only allows SHA256 } - err := validateCertificateSignatureAlgorithms(certs, allowed) + err := dtlscrypto.ValidateCertificateSignatureAlgorithms(certs, allowed) assert.ErrorIs(t, err, dtlserrors.ErrInvalidCertificateSignatureAlgorithm) }) @@ -449,7 +454,7 @@ func TestValidateCertificateSignatureAlgorithms(t *testing.T) { {Hash: hash.SHA256, Signature: signature.ECDSA}, {Hash: hash.SHA384, Signature: signature.ECDSA}, } - err := validateCertificateSignatureAlgorithms(certs, allowed) + err := dtlscrypto.ValidateCertificateSignatureAlgorithms(certs, allowed) assert.NoError(t, err) }) @@ -461,7 +466,7 @@ func TestValidateCertificateSignatureAlgorithms(t *testing.T) { allowed := []signaturehash.Algorithm{ {Hash: hash.SHA256, Signature: signature.RSA}, } - err := validateCertificateSignatureAlgorithms(certs, allowed) + err := dtlscrypto.ValidateCertificateSignatureAlgorithms(certs, allowed) assert.NoError(t, err) }) @@ -473,7 +478,7 @@ func TestValidateCertificateSignatureAlgorithms(t *testing.T) { allowed := []signaturehash.Algorithm{ {Hash: hash.None, Signature: signature.Ed25519}, } - err := validateCertificateSignatureAlgorithms(certs, allowed) + err := dtlscrypto.ValidateCertificateSignatureAlgorithms(certs, allowed) assert.NoError(t, err) }) @@ -485,7 +490,7 @@ func TestValidateCertificateSignatureAlgorithms(t *testing.T) { allowed := []signaturehash.Algorithm{ {Hash: hash.SHA256, Signature: signature.RSA}, } - err := validateCertificateSignatureAlgorithms(certs, allowed) + err := dtlscrypto.ValidateCertificateSignatureAlgorithms(certs, allowed) assert.Error(t, err) // Should error from FromCertificate, not from algorithm mismatch }) @@ -499,7 +504,7 @@ func TestValidateCertificateSignatureAlgorithms(t *testing.T) { {Hash: hash.SHA384, Signature: signature.ECDSA}, // Different algorithm } // Should pass because single root cert is not validated - err := validateCertificateSignatureAlgorithms(certs, allowed) + err := dtlscrypto.ValidateCertificateSignatureAlgorithms(certs, allowed) assert.NoError(t, err) }) } diff --git a/flight.go b/flight.go deleted file mode 100644 index 0544177d9..000000000 --- a/flight.go +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-FileCopyrightText: 2026 The Pion community -// SPDX-License-Identifier: MIT - -package dtls - -/* - DTLS messages are grouped into a series of message flights, according - to the diagrams below. Although each flight of messages may consist - of a number of messages, they should be viewed as monolithic for the - purpose of timeout and retransmission. - https://tools.ietf.org/html/rfc4347#section-4.2.4 - - Message flights for full handshake: - - Client Server - ------ ------ - Waiting Flight 0 - - ClientHello --------> Flight 1 - - <------- HelloVerifyRequest Flight 2 - - ClientHello --------> Flight 3 - - ServerHello \ - Certificate* \ - ServerKeyExchange* Flight 4 - CertificateRequest* / - <-------- ServerHelloDone / - - Certificate* \ - ClientKeyExchange \ - CertificateVerify* Flight 5 - [ChangeCipherSpec] / - Finished --------> / - - [ChangeCipherSpec] \ Flight 6 - <-------- Finished / - - Message flights for session-resuming handshake (no cookie exchange): - - Client Server - ------ ------ - Waiting Flight 0 - - ClientHello --------> Flight 1 - - ServerHello \ - [ChangeCipherSpec] Flight 4b - <-------- Finished / - - [ChangeCipherSpec] \ Flight 5b - Finished --------> / - - [ChangeCipherSpec] \ Flight 6 - <-------- Finished / -*/ - -type flightVal uint8 - -const ( - flight0 flightVal = iota + 1 - flight1 - flight2 - flight3 - flight4 - flight4b - flight5 - flight5b - flight6 -) - -func (f flightVal) String() string { //nolint:cyclop - switch f { - case flight0: - return "Flight 0" - case flight1: - return "Flight 1" - case flight2: - return "Flight 2" - case flight3: - return "Flight 3" - case flight4: - return "Flight 4" - case flight4b: - return "Flight 4b" - case flight5: - return "Flight 5" - case flight5b: - return "Flight 5b" - case flight6: - return "Flight 6" - default: - return "Invalid Flight" - } -} - -func (f flightVal) isLastSendFlight() bool { - return f == flight6 || f == flight5b -} - -func (f flightVal) isLastRecvFlight() bool { - return f == flight5 || f == flight4b -} diff --git a/flight13_test_helpers_test.go b/flight13_test_helpers_test.go new file mode 100644 index 000000000..d29c584f6 --- /dev/null +++ b/flight13_test_helpers_test.go @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2026 The Pion community +// SPDX-License-Identifier: MIT + +package dtls + +import ( + "context" + + dtlsconfig "github.com/pion/dtls/v3/internal/config" + dtlsflight "github.com/pion/dtls/v3/internal/flight" + dtlsflight13 "github.com/pion/dtls/v3/internal/flight/flight13" + dtlsstate "github.com/pion/dtls/v3/internal/state" + "github.com/pion/dtls/v3/pkg/protocol/alert" + "github.com/stretchr/testify/require" +) + +const renegotiationInfoSCSV uint16 = 0x00ff + +type handshakeContext13 struct { + state *dtlsstate.State + cache *dtlsflight.Cache + cfg *handshakeConfig + transcript *handshakeTranscript13 +} + +func (s *handshakeFSM13) flightContext() *handshakeContext13 { + return &handshakeContext13{ + state: s.state, + cache: s.cache, + cfg: s.cfg, + transcript: s.transcript, + } +} + +func flight13ParseForTest( + testingT require.TestingT, + flight dtlsflight.Flight13, + ctx context.Context, + flightCtx *handshakeContext13, +) (dtlsflight.Flight13, *alert.Alert, error) { + if helper, ok := testingT.(interface{ Helper() }); ok { + helper.Helper() + } + + nextFlight, dtlsAlert, err, ok := dtlsflight13.Parse( + ctx, + flight, + nil, + flightCtx.state, + flightCtx.cache, + flightCtx.cfg, + func(cipherSuite dtlsconfig.CipherSuite, items []*dtlsflight.HandshakeCacheItem) error { + return appendInboundHandshakeCacheItems13(flightCtx.transcript, cipherSuite, items) + }, + func(state *dtlsstate.State) error { + return deriveAndStoreHandshakeTrafficSecrets13(state, flightCtx.transcript) + }, + ) + require.True(testingT, ok) + + return nextFlight, dtlsAlert, err +} + +func flight13GenerateForTest( + testingT require.TestingT, + flight dtlsflight.Flight13, + flightCtx *handshakeContext13, +) ([]*dtlsflight.Packet, *alert.Alert, error) { + if helper, ok := testingT.(interface{ Helper() }); ok { + helper.Helper() + } + + gen, _, ok := dtlsflight13.GetGenerator(flight) + require.True(testingT, ok) + + return gen(nil, flightCtx.state, flightCtx.cache, flightCtx.cfg) +} diff --git a/flight6handler.go b/flight6handler.go deleted file mode 100644 index 5f0ee6388..000000000 --- a/flight6handler.go +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-FileCopyrightText: 2026 The Pion community -// SPDX-License-Identifier: MIT - -package dtls - -import ( - "context" - - dtlsstate "github.com/pion/dtls/v3/internal/state" - "github.com/pion/dtls/v3/pkg/crypto/prf" - "github.com/pion/dtls/v3/pkg/protocol" - "github.com/pion/dtls/v3/pkg/protocol/alert" - "github.com/pion/dtls/v3/pkg/protocol/handshake" - "github.com/pion/dtls/v3/pkg/protocol/recordlayer" -) - -func flight6Parse( - _ context.Context, - _ flightConn, - state *dtlsstate.State, - cache *handshakeCache, - cfg *handshakeConfig, -) (flightVal, *alert.Alert, error) { - _, msgs, ok := cache.fullPullMap(state.HandshakeRecvSequence-1, state.CipherSuite, - handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, true, false}, - ) - if !ok { - // No valid message received. Keep reading - return 0, nil, nil - } - - if _, ok = msgs[handshake.TypeFinished].(*handshake.MessageFinished); !ok { - return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil - } - - // Other party may re-transmit the last flight. Keep state to be flight6. - return flight6, nil, nil -} - -func flight6Generate( - _ flightConn, - state *dtlsstate.State, - cache *handshakeCache, - cfg *handshakeConfig, -) ([]*packet, *alert.Alert, error) { - var pkts []*packet - - pkts = append(pkts, - &packet{ - record: &recordlayer.RecordLayer{ - Header: recordlayer.Header{ - Version: protocol.Version1_2, - }, - Content: &protocol.ChangeCipherSpec{}, - }, - }) - - if len(state.LocalVerifyData) == 0 { - plainText := cache.pullAndMerge( - handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false}, - handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false}, - handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, false, false}, - handshakeCachePullRule{handshake.TypeServerKeyExchange, cfg.initialEpoch, false, false}, - handshakeCachePullRule{handshake.TypeCertificateRequest, cfg.initialEpoch, false, false}, - handshakeCachePullRule{handshake.TypeServerHelloDone, cfg.initialEpoch, false, false}, - handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, true, false}, - handshakeCachePullRule{handshake.TypeClientKeyExchange, cfg.initialEpoch, true, false}, - handshakeCachePullRule{handshake.TypeCertificateVerify, cfg.initialEpoch, true, false}, - handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, true, false}, - ) - - var err error - state.LocalVerifyData, err = prf.VerifyDataServer(state.MasterSecret, plainText, state.CipherSuite.HashFunc()) - if err != nil { - return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err - } - } - - pkts = append(pkts, - &packet{ - record: &recordlayer.RecordLayer{ - Header: recordlayer.Header{ - Version: protocol.Version1_2, - Epoch: 1, - }, - Content: &handshake.Handshake{ - Message: &handshake.MessageFinished{ - VerifyData: state.LocalVerifyData, - }, - }, - }, - shouldWrapCID: len(state.RemoteConnectionID) > 0, - shouldEncrypt: true, - resetLocalSequenceNumber: true, - }, - ) - - return pkts, nil, nil -} diff --git a/flight_13.go b/flight_13.go deleted file mode 100644 index 396a28f10..000000000 --- a/flight_13.go +++ /dev/null @@ -1,194 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -package dtls - -type flightVal13 uint8 - -/* -// [RFC9147 Section-5.7] - -Full DTLS Handshake (with Cookie Exchange): - -Client Server - - +----------+ - Waiting | Flight 0 | - +----------+ - - +----------+ - ClientHello | Flight 1 | - --------> +----------+ - - +----------+ - <-------- HelloRetryRequest | Flight 2 | - + cookie +----------+ - - - +----------+ -ClientHello | Flight 3 | - + cookie --------> +----------+ - - - - ServerHello - {EncryptedExtensions} +----------+ - {CertificateRequest*} | Flight 4 | - {Certificate*} +----------+ - {CertificateVerify*} - {Finished} - <-------- [Application Data*] - - - - {Certificate*} +----------+ - {CertificateVerify*} | Flight 5 | - {Finished} --------> +----------+ - [Application Data] - +----------+ - <-------- [ACK] | Flight 6 | - [Application Data*] +----------+ - - [Application Data] <-------> [Application Data] - - - - -Resumption and PSK Handshake (without Cookie Exchange): - -Client Server - - ClientHello +-----------+ - + pre_shared_key | Flight 3a | - + psk_key_exchange_modes +-----------+ - + key_share* --------> - - - ServerHello - + pre_shared_key +-----------+ - + key_share* | Flight 4a | - {EncryptedExtensions} +-----------+ - <-------- {Finished} - [Application Data*] - +-----------+ - {Finished} --------> | Flight 5a | - [Application Data*] +-----------+ - - +-----------+ - <-------- [ACK] | Flight 6a | - [Application Data*] +-----------+ - - [Application Data] <-------> [Application Data] - - -Zero-RTT Handshake: - -Client Server - - ClientHello - + early_data - + psk_key_exchange_modes +-----------+ - + key_share* | Flight 3b | - + pre_shared_key +-----------+ - (Application Data*) --------> - - ServerHello - + pre_shared_key - + key_share* +-----------+ - {EncryptedExtensions} | Flight 4b | - {Finished} +-----------+ - <-------- [Application Data*] - - - +-----------+ - {Finished} --------> | Flight 5b | - [Application Data*] +-----------+ - - +-----------+ - <-------- [ACK] | Flight 6b | - [Application Data*] +-----------+ - - [Application Data] <-------> [Application Data] - - -NewSessionTicket Message: - -Client Server - - +-----------+ - <-------- [NewSessionTicket] | Flight 4c | - +-----------+ - - +-----------+ -[ACK] --------> | Flight 5c | - +-----------+ -*/ - -const ( - flight13_0 flightVal13 = iota + 1 - flight13_1 - flight13_2 - flight13_3 - flight13_3a - flight13_3b - flight13_4 - flight13_4a - flight13_4b - flight13_4c - flight13_5 - flight13_5a - flight13_5b - flight13_5c - flight13_6 - flight13_6a - flight13_6b -) - -func (f flightVal13) String() string { //nolint:cyclop - switch f { - case flight13_0: - return "Flight13 0" - case flight13_1: - return "Flight13 1" - case flight13_2: - return "Flight13 2" - case flight13_3: - return "Flight13 3" - case flight13_3a: - return "Flight13 3a" - case flight13_3b: - return "Flight13 3b" - case flight13_4: - return "Flight13 4" - case flight13_4a: - return "Flight13 4a" - case flight13_4b: - return "Flight13 4b" - case flight13_4c: - return "Flight13 4c" - case flight13_5: - return "Flight13 5" - case flight13_5a: - return "Flight13 5a" - case flight13_5b: - return "Flight13 5b" - case flight13_5c: - return "Flight13 5c" - case flight13_6: - return "Flight13 6" - case flight13_6a: - return "Flight13 6a" - case flight13_6b: - return "Flight13 6b" - default: - return "Invalid Flight" - } -} - -func (f flightVal13) isLastSendFlight() bool { // nolint: unused - return f == flight13_6 || f == flight13_6a || f == flight13_6b || f == flight13_5c -} - -func (f flightVal13) isLastRecvFlight() bool { // nolint: unused - return f == flight13_5 || f == flight13_5a || f == flight13_5b || f == flight13_4c -} diff --git a/flighthandler.go b/flighthandler.go deleted file mode 100644 index 50f0af450..000000000 --- a/flighthandler.go +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-FileCopyrightText: 2026 The Pion community -// SPDX-License-Identifier: MIT - -package dtls - -import ( - "context" - - dtlserrors "github.com/pion/dtls/v3/internal/errors" - dtlsstate "github.com/pion/dtls/v3/internal/state" - "github.com/pion/dtls/v3/pkg/protocol/alert" -) - -// Parse received handshakes and return next flightVal. -type flightParser func( - context.Context, - flightConn, - *dtlsstate.State, - *handshakeCache, - *handshakeConfig, -) (flightVal, *alert.Alert, error) - -// Generate flights. -type flightGenerator func( - flightConn, - *dtlsstate.State, - *handshakeCache, - *handshakeConfig, -) ([]*packet, *alert.Alert, error) - -func (f flightVal) getFlightParser() (flightParser, error) { //nolint:cyclop - switch f { - case flight0: - return flight0Parse, nil - case flight1: - return flight1Parse, nil - case flight2: - return flight2Parse, nil - case flight3: - return flight3Parse, nil - case flight4: - return flight4Parse, nil - case flight4b: - return flight4bParse, nil - case flight5: - return flight5Parse, nil - case flight5b: - return flight5bParse, nil - case flight6: - return flight6Parse, nil - default: - return nil, dtlserrors.ErrInvalidFlight - } -} - -func (f flightVal) getFlightGenerator() (gen flightGenerator, retransmit bool, err error) { //nolint:cyclop - switch f { - case flight0: - return flight0Generate, true, nil - case flight1: - return flight1Generate, true, nil - case flight2: - // https://tools.ietf.org/html/rfc6347#section-3.2.1 - // HelloVerifyRequests must not be retransmitted. - return flight2Generate, false, nil - case flight3: - return flight3Generate, true, nil - case flight4: - return flight4Generate, true, nil - case flight4b: - return flight4bGenerate, true, nil - case flight5: - return flight5Generate, true, nil - case flight5b: - return flight5bGenerate, true, nil - case flight6: - return flight6Generate, true, nil - default: - return nil, false, dtlserrors.ErrInvalidFlight - } -} diff --git a/flighthandler_13.go b/flighthandler_13.go deleted file mode 100644 index 336ebe370..000000000 --- a/flighthandler_13.go +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -package dtls - -import ( - "context" - - dtlserrors "github.com/pion/dtls/v3/internal/errors" - dtlsstate "github.com/pion/dtls/v3/internal/state" - "github.com/pion/dtls/v3/pkg/protocol/alert" -) - -type handshakeContext13 struct { - state *dtlsstate.State - cache *handshakeCache - cfg *handshakeConfig - transcript *handshakeTranscript13 -} - -// Parse received handshakes and return next flightVal. -type flightParser13 func( //nolint:unused - context.Context, - flightConn, - *handshakeContext13, -) (flightVal13, *alert.Alert, error) - -//nolint:unused -type flightGenerator13 func(flightConn, *handshakeContext13) ([]*packet, *alert.Alert, error) - -//nolint:unused -func (f flightVal13) getFlightParser13() (flightParser13, error) { - switch f { - case flight13_0: - return flight13_0Parse, nil - case flight13_1: - return flight13_1Parse, nil - case flight13_2: - return flight13_2Parse, nil - case flight13_3: - return flight13_3Parse, nil - - default: - return nil, dtlserrors.ErrFlightUnimplemented13 - } -} - -func (f flightVal13) getFlightGenerator13() (gen flightGenerator13, retransmit bool, err error) { - switch f { - case flight13_0: - return flight13_0Generate, true, nil - case flight13_1: - return flight13_1Generate, true, nil - case flight13_2: - return flight13_2Generate, false, nil - case flight13_3: - return flight13_3Generate, true, nil - default: - return nil, false, dtlserrors.ErrFlightUnimplemented13 - } -} diff --git a/flighthandlers_client_13_test.go b/flighthandlers_client_13_test.go index 1317a56b2..76a25b7c3 100644 --- a/flighthandlers_client_13_test.go +++ b/flighthandlers_client_13_test.go @@ -8,6 +8,7 @@ import ( "testing" dtlserrors "github.com/pion/dtls/v3/internal/errors" + dtlsflight "github.com/pion/dtls/v3/internal/flight" dtlsstate "github.com/pion/dtls/v3/internal/state" "github.com/pion/dtls/v3/pkg/crypto/elliptic" "github.com/pion/dtls/v3/pkg/crypto/prf" @@ -71,7 +72,7 @@ func marshalServerHelloWithSequence( ) []byte { t.Helper() - cipherSuiteID := uint16(cfg.localCipherSuites[0].ID()) + cipherSuiteID := uint16(cfg.LocalCipherSuites[0].ID()) serverHello := &handshake.MessageServerHello{ Version: protocol.Version1_2, Random: random, @@ -93,7 +94,7 @@ func generateFlight13_1ClientHello(t *testing.T, cfg *handshakeConfig) *handshak state := &dtlsstate.State{} - pkts, dtlsAlert, err := flight13_1Generate(nil, &handshakeContext13{ + pkts, dtlsAlert, err := flight13GenerateForTest(t, dtlsflight.Flight13_1, &handshakeContext13{ state: state, cfg: cfg, }) @@ -102,7 +103,7 @@ func generateFlight13_1ClientHello(t *testing.T, cfg *handshakeConfig) *handshak require.Nil(t, dtlsAlert) require.Len(t, pkts, 1) - hand, ok := pkts[0].record.Content.(*handshake.Handshake) + hand, ok := pkts[0].Record.Content.(*handshake.Handshake) require.True(t, ok) raw, err := hand.Marshal() require.NoError(t, err) @@ -119,7 +120,7 @@ func TestFlight13_1GenerateClientHelloUsesSupportedVersionsVector(t *testing.T) cfg := testHandshakeConfig13(t) state := &dtlsstate.State{} - pkts, dtlsAlert, err := flight13_1Generate(nil, &handshakeContext13{ + pkts, dtlsAlert, err := flight13GenerateForTest(t, dtlsflight.Flight13_1, &handshakeContext13{ state: state, cfg: cfg, }) @@ -128,7 +129,7 @@ func TestFlight13_1GenerateClientHelloUsesSupportedVersionsVector(t *testing.T) require.Nil(t, dtlsAlert) require.Len(t, pkts, 1) - hand, ok := pkts[0].record.Content.(*handshake.Handshake) + hand, ok := pkts[0].Record.Content.(*handshake.Handshake) require.True(t, ok) raw, err := hand.Marshal() require.NoError(t, err) @@ -153,7 +154,7 @@ func TestFlight13_1GenerateClientHelloUsesSupportedVersionsVector(t *testing.T) func TestFlight13_1GenerateClientHelloIncludesSignatureAlgorithms(t *testing.T) { cfg := testHandshakeConfig13(t) - cfg.localCertSignatureSchemes = cfg.localSignatureSchemes[:1] + cfg.LocalCertSignatureSchemes = cfg.LocalSignatureSchemes[:1] clientHello := generateFlight13_1ClientHello(t, cfg) @@ -169,9 +170,9 @@ func TestFlight13_1GenerateClientHelloIncludesSignatureAlgorithms(t *testing.T) } require.NotNil(t, signatureAlgorithms) - assert.Equal(t, cfg.localSignatureSchemes, signatureAlgorithms.SignatureHashAlgorithms) + assert.Equal(t, cfg.LocalSignatureSchemes, signatureAlgorithms.SignatureHashAlgorithms) require.NotNil(t, signatureAlgorithmsCert) - assert.Equal(t, cfg.localCertSignatureSchemes, signatureAlgorithmsCert.SignatureHashAlgorithms) + assert.Equal(t, cfg.LocalCertSignatureSchemes, signatureAlgorithmsCert.SignatureHashAlgorithms) } func TestFlight13_1GenerateClientHelloIncludesSupportedGroups(t *testing.T) { @@ -189,14 +190,14 @@ func TestFlight13_1GenerateClientHelloIncludesSupportedGroups(t *testing.T) { } require.NotNil(t, supportedGroups) - assert.Equal(t, cfg.ellipticCurves, supportedGroups.EllipticCurves) + assert.Equal(t, cfg.EllipticCurves, supportedGroups.EllipticCurves) } func TestFlight13_1GenerateRetainsPrivateKeysForAdvertisedShares(t *testing.T) { cfg := testHandshakeConfig13(t) state := &dtlsstate.State{} - pkts, dtlsAlert, err := flight13_1Generate(nil, &handshakeContext13{ + pkts, dtlsAlert, err := flight13GenerateForTest(t, dtlsflight.Flight13_1, &handshakeContext13{ state: state, cfg: cfg, }) @@ -205,7 +206,7 @@ func TestFlight13_1GenerateRetainsPrivateKeysForAdvertisedShares(t *testing.T) { require.Nil(t, dtlsAlert) require.Len(t, pkts, 1) - hand, ok := pkts[0].record.Content.(*handshake.Handshake) + hand, ok := pkts[0].Record.Content.(*handshake.Handshake) require.True(t, ok) raw, err := hand.Marshal() require.NoError(t, err) @@ -224,7 +225,7 @@ func TestFlight13_1GenerateRetainsPrivateKeysForAdvertisedShares(t *testing.T) { } } require.NotNil(t, keyShare) - require.Len(t, keyShare.ClientShares, len(cfg.ellipticCurves)) + require.Len(t, keyShare.ClientShares, len(cfg.EllipticCurves)) require.Len(t, state.LocalKeyEntries, len(keyShare.ClientShares)) require.Len(t, state.LocalKeypairs, len(keyShare.ClientShares)) @@ -265,10 +266,10 @@ func TestFlight13_1ParseStoresHelloRetryRequestSelectedGroup(t *testing.T) { ) state := &dtlsstate.State{} - cache := newHandshakeCache() - cache.push(rawServerHello, cfg.initialEpoch, 0, handshake.TypeServerHello, false) + cache := dtlsflight.NewCache() + cache.Push(rawServerHello, cfg.InitialEpoch, 0, handshake.TypeServerHello, false) - nextFlight, dtlsAlert, err := flight13_1Parse(context.Background(), nil, &handshakeContext13{ + nextFlight, dtlsAlert, err := flight13ParseForTest(t, dtlsflight.Flight13_1, context.Background(), &handshakeContext13{ state: state, cache: cache, cfg: cfg, @@ -276,7 +277,7 @@ func TestFlight13_1ParseStoresHelloRetryRequestSelectedGroup(t *testing.T) { require.NoError(t, err) require.Nil(t, dtlsAlert) - assert.Equal(t, flight13_3, nextFlight) + assert.Equal(t, dtlsflight.Flight13_3, nextFlight) entries := *state.RemoteKeyEntries require.Len(t, entries, 1) assert.Equal(t, selectedGroup, entries[0].Group) @@ -296,10 +297,10 @@ func TestFlight13_1ParseRejectsHelloRetryRequestWithoutSupportedVersions(t *test ) state := &dtlsstate.State{} - cache := newHandshakeCache() - cache.push(rawServerHello, cfg.initialEpoch, 0, handshake.TypeServerHello, false) + cache := dtlsflight.NewCache() + cache.Push(rawServerHello, cfg.InitialEpoch, 0, handshake.TypeServerHello, false) - nextFlight, dtlsAlert, err := flight13_1Parse(context.Background(), nil, &handshakeContext13{ + nextFlight, dtlsAlert, err := flight13ParseForTest(t, dtlsflight.Flight13_1, context.Background(), &handshakeContext13{ state: state, cache: cache, cfg: cfg, @@ -330,10 +331,10 @@ func TestFlight13_1ParseRejectsHelloRetryRequestWithWrongSelectedVersion(t *test ) state := &dtlsstate.State{} - cache := newHandshakeCache() - cache.push(rawServerHello, cfg.initialEpoch, 0, handshake.TypeServerHello, false) + cache := dtlsflight.NewCache() + cache.Push(rawServerHello, cfg.InitialEpoch, 0, handshake.TypeServerHello, false) - nextFlight, dtlsAlert, err := flight13_1Parse(context.Background(), nil, &handshakeContext13{ + nextFlight, dtlsAlert, err := flight13ParseForTest(t, dtlsflight.Flight13_1, context.Background(), &handshakeContext13{ state: state, cache: cache, cfg: cfg, @@ -369,10 +370,10 @@ func TestFlight13_1ParseRejectsHelloRetryRequestWithClientHelloSupportedVersions ) state := &dtlsstate.State{} - cache := newHandshakeCache() - cache.push(rawServerHello, cfg.initialEpoch, 0, handshake.TypeServerHello, false) + cache := dtlsflight.NewCache() + cache.Push(rawServerHello, cfg.InitialEpoch, 0, handshake.TypeServerHello, false) - nextFlight, dtlsAlert, err := flight13_1Parse(context.Background(), nil, &handshakeContext13{ + nextFlight, dtlsAlert, err := flight13ParseForTest(t, dtlsflight.Flight13_1, context.Background(), &handshakeContext13{ state: state, cache: cache, cfg: cfg, @@ -388,8 +389,8 @@ func TestFlight13_1ParseRejectsHelloRetryRequestWithClientHelloSupportedVersions func TestPickVersionFromServerResponseRejectsHelloRetryRequestWithoutSupportedVersions(t *testing.T) { cfg := testHandshakeConfig13(t) - cfg.minVersion = protocol.Version1_2 - cfg.maxVersion = protocol.Version1_3 + cfg.MinVersion = protocol.Version1_2 + cfg.MaxVersion = protocol.Version1_3 selectedGroup := elliptic.P384 rawServerHello := marshalHelloRetryRequestServerHello( @@ -401,10 +402,10 @@ func TestPickVersionFromServerResponseRejectsHelloRetryRequestWithoutSupportedVe ) conn := &Conn{ - handshakeCache: newHandshakeCache(), + handshakeCache: dtlsflight.NewCache(), handshakeConfig: cfg, } - conn.handshakeCache.push(rawServerHello, cfg.initialEpoch, 0, handshake.TypeServerHello, false) + conn.handshakeCache.Push(rawServerHello, cfg.InitialEpoch, 0, handshake.TypeServerHello, false) ok, err := conn.pickVersionFromServerResponse() @@ -415,8 +416,8 @@ func TestPickVersionFromServerResponseRejectsHelloRetryRequestWithoutSupportedVe func TestPickVersionFromServerResponseRejectsServerHelloWithClientHelloSupportedVersionsEncoding(t *testing.T) { cfg := testHandshakeConfig13(t) - cfg.minVersion = protocol.Version1_2 - cfg.maxVersion = protocol.Version1_3 + cfg.MinVersion = protocol.Version1_2 + cfg.MaxVersion = protocol.Version1_3 random := handshake.Random{RandomBytes: [handshake.RandomBytesLength]byte{0x01}} rawServerHello := marshalServerHello( @@ -437,10 +438,10 @@ func TestPickVersionFromServerResponseRejectsServerHelloWithClientHelloSupported ) conn := &Conn{ - handshakeCache: newHandshakeCache(), + handshakeCache: dtlsflight.NewCache(), handshakeConfig: cfg, } - conn.handshakeCache.push(rawServerHello, cfg.initialEpoch, 0, handshake.TypeServerHello, false) + conn.handshakeCache.Push(rawServerHello, cfg.InitialEpoch, 0, handshake.TypeServerHello, false) ok, err := conn.pickVersionFromServerResponse() @@ -454,7 +455,7 @@ func TestFlight13_3GenerateRejectsWithoutCommonVersion(t *testing.T) { state := &dtlsstate.State{} require.NoError(t, state.LocalRandom.Populate()) - pkts, dtlsAlert, err := flight13_3Generate(nil, &handshakeContext13{ + pkts, dtlsAlert, err := flight13GenerateForTest(t, dtlsflight.Flight13_3, &handshakeContext13{ state: state, cfg: cfg, }) @@ -472,7 +473,7 @@ func TestFlight13_3GenerateIncludesCookieAndSupportedVersions(t *testing.T) { } require.NoError(t, state.LocalRandom.Populate()) - pkts, dtlsAlert, err := flight13_3Generate(nil, &handshakeContext13{ + pkts, dtlsAlert, err := flight13GenerateForTest(t, dtlsflight.Flight13_3, &handshakeContext13{ state: state, cfg: cfg, }) @@ -481,7 +482,7 @@ func TestFlight13_3GenerateIncludesCookieAndSupportedVersions(t *testing.T) { require.Nil(t, dtlsAlert) require.Len(t, pkts, 1) - hand, ok := pkts[0].record.Content.(*handshake.Handshake) + hand, ok := pkts[0].Record.Content.(*handshake.Handshake) require.True(t, ok) raw, err := hand.Marshal() require.NoError(t, err) @@ -512,7 +513,7 @@ func TestFlight13_3GenerateIncludesCookieAndSupportedVersions(t *testing.T) { } } require.NotNil(t, signatureAlgorithms) - assert.Equal(t, cfg.localSignatureSchemes, signatureAlgorithms.SignatureHashAlgorithms) + assert.Equal(t, cfg.LocalSignatureSchemes, signatureAlgorithms.SignatureHashAlgorithms) var cookieExt *extension.CookieExt for _, ext := range clientHello.Extensions { @@ -541,7 +542,7 @@ func TestFlight13_3GeneratePrioritizesHelloRetryRequestSelectedGroup(t *testing. } require.NoError(t, state.LocalRandom.Populate()) - pkts, dtlsAlert, err := flight13_3Generate(nil, &handshakeContext13{ + pkts, dtlsAlert, err := flight13GenerateForTest(t, dtlsflight.Flight13_3, &handshakeContext13{ state: state, cfg: cfg, }) @@ -550,7 +551,7 @@ func TestFlight13_3GeneratePrioritizesHelloRetryRequestSelectedGroup(t *testing. require.Nil(t, dtlsAlert) require.Len(t, pkts, 1) - hand, ok := pkts[0].record.Content.(*handshake.Handshake) + hand, ok := pkts[0].Record.Content.(*handshake.Handshake) require.True(t, ok) raw, err := hand.Marshal() require.NoError(t, err) @@ -594,7 +595,7 @@ func TestFlight13_3GenerateDoesNotRegenerateAlreadyAdvertisedGroup(t *testing.T) } require.NoError(t, state.LocalRandom.Populate()) - pkts, dtlsAlert, err := flight13_3Generate(nil, &handshakeContext13{ + pkts, dtlsAlert, err := flight13GenerateForTest(t, dtlsflight.Flight13_3, &handshakeContext13{ state: state, cfg: cfg, }) @@ -603,7 +604,7 @@ func TestFlight13_3GenerateDoesNotRegenerateAlreadyAdvertisedGroup(t *testing.T) require.Nil(t, dtlsAlert) require.Len(t, pkts, 1) - hand, ok := pkts[0].record.Content.(*handshake.Handshake) + hand, ok := pkts[0].Record.Content.(*handshake.Handshake) require.True(t, ok) raw, err := hand.Marshal() require.NoError(t, err) @@ -630,13 +631,13 @@ func TestFlight13_3ParseNegotiatesVersionCipherAndKeyShare(t *testing.T) { cfg := testHandshakeConfig13(t) state := &dtlsstate.State{} transcript := newHandshakeTranscript13() - clientHello, _, err := flight13_1Generate(nil, &handshakeContext13{state: state, cfg: cfg}) + clientHello, _, err := flight13GenerateForTest(t, dtlsflight.Flight13_1, &handshakeContext13{state: state, cfg: cfg}) require.NoError(t, err) appended, err := appendClientHelloInitialFlights13(transcript, clientHello) require.NoError(t, err) require.True(t, appended) - group := cfg.ellipticCurves[0] + group := cfg.EllipticCurves[0] serverKeypair, err := elliptic.GenerateKeypair(group) require.NoError(t, err) @@ -646,9 +647,9 @@ func TestFlight13_3ParseNegotiatesVersionCipherAndKeyShare(t *testing.T) { &extension.KeyShare{ServerShare: &extension.KeyShareEntry{Group: group, KeyExchange: serverKeypair.PublicKey}}, }) - cache := newHandshakeCache() - cache.push(rawServerHello, cfg.initialEpoch, 0, handshake.TypeServerHello, false) - nextFlight, dtlsAlert, err := flight13_3Parse(context.Background(), nil, &handshakeContext13{ + cache := dtlsflight.NewCache() + cache.Push(rawServerHello, cfg.InitialEpoch, 0, handshake.TypeServerHello, false) + nextFlight, dtlsAlert, err := flight13ParseForTest(t, dtlsflight.Flight13_3, context.Background(), &handshakeContext13{ state: state, cache: cache, cfg: cfg, @@ -657,12 +658,12 @@ func TestFlight13_3ParseNegotiatesVersionCipherAndKeyShare(t *testing.T) { require.NoError(t, err) require.Nil(t, dtlsAlert) - assert.Equal(t, flight13_5, nextFlight) + assert.Equal(t, dtlsflight.Flight13_5, nextFlight) assert.Equal(t, protocol.Version1_3, state.LocalVersion) assert.Equal(t, []protocol.Version{protocol.Version1_3}, state.RemoteVersions) require.NotNil(t, state.CipherSuite) - assert.Equal(t, cfg.localCipherSuites[0].ID(), state.CipherSuite.ID()) + assert.Equal(t, cfg.LocalCipherSuites[0].ID(), state.CipherSuite.ID()) assert.Equal(t, group, state.NamedCurve) assert.Equal(t, random.RandomBytes, state.RemoteRandom.RandomBytes) require.NotNil(t, state.RemoteKeyEntries) @@ -688,7 +689,7 @@ func TestFlight13ClientParseAppendsNoHRRTranscriptOrder(t *testing.T) { state := &dtlsstate.State{} transcript := newHandshakeTranscript13() - pkts, dtlsAlert, err := flight13_1Generate(nil, &handshakeContext13{ + pkts, dtlsAlert, err := flight13GenerateForTest(t, dtlsflight.Flight13_1, &handshakeContext13{ state: state, cfg: cfg, transcript: transcript, @@ -700,7 +701,7 @@ func TestFlight13ClientParseAppendsNoHRRTranscriptOrder(t *testing.T) { require.True(t, appended) clientHelloCanonical := canonicalPacketHandshake13(t, pkts[0]) - group := cfg.ellipticCurves[0] + group := cfg.EllipticCurves[0] serverKeypair, err := elliptic.GenerateKeypair(group) require.NoError(t, err) rawServerHello := marshalServerHello(t, cfg, handshake.Random{ @@ -712,9 +713,9 @@ func TestFlight13ClientParseAppendsNoHRRTranscriptOrder(t *testing.T) { serverHelloCanonical, err := canonicalHandshake13(rawServerHello) require.NoError(t, err) - cache := newHandshakeCache() - cache.push(rawServerHello, cfg.initialEpoch, 0, handshake.TypeServerHello, false) - nextFlight, dtlsAlert, err := flight13_1Parse(context.Background(), nil, &handshakeContext13{ + cache := dtlsflight.NewCache() + cache.Push(rawServerHello, cfg.InitialEpoch, 0, handshake.TypeServerHello, false) + nextFlight, dtlsAlert, err := flight13ParseForTest(t, dtlsflight.Flight13_1, context.Background(), &handshakeContext13{ state: state, cache: cache, cfg: cfg, @@ -723,7 +724,7 @@ func TestFlight13ClientParseAppendsNoHRRTranscriptOrder(t *testing.T) { require.NoError(t, err) require.Nil(t, dtlsAlert) - assert.Equal(t, flight13_5, nextFlight) + assert.Equal(t, dtlsflight.Flight13_5, nextFlight) assert.Equal(t, []transcriptMessage13{ {id: transcriptMessageID13{sender: transcriptClient13, seq: 0}, typ: handshake.TypeClientHello}, {id: transcriptMessageID13{sender: transcriptServer13, seq: 0}, typ: handshake.TypeServerHello}, @@ -736,7 +737,7 @@ func TestFlight13ClientParseAppendsHRRTranscriptOrder(t *testing.T) { state := &dtlsstate.State{} transcript := newHandshakeTranscript13() - pkts, dtlsAlert, err := flight13_1Generate(nil, &handshakeContext13{ + pkts, dtlsAlert, err := flight13GenerateForTest(t, dtlsflight.Flight13_1, &handshakeContext13{ state: state, cfg: cfg, transcript: transcript, @@ -748,7 +749,7 @@ func TestFlight13ClientParseAppendsHRRTranscriptOrder(t *testing.T) { require.True(t, appended) clientHello1Canonical := canonicalPacketHandshake13(t, pkts[0]) - group := cfg.ellipticCurves[0] + group := cfg.EllipticCurves[0] rawHelloRetryRequest := marshalHelloRetryRequestServerHello(t, cfg, []extension.Extension{ &extension.SupportedVersions{Versions: []protocol.Version{protocol.Version1_3}, SelectedVersion: true}, &extension.KeyShare{SelectedGroup: &group}, @@ -756,9 +757,9 @@ func TestFlight13ClientParseAppendsHRRTranscriptOrder(t *testing.T) { helloRetryRequestCanonical, err := canonicalHandshake13(rawHelloRetryRequest) require.NoError(t, err) - cache := newHandshakeCache() - cache.push(rawHelloRetryRequest, cfg.initialEpoch, 0, handshake.TypeServerHello, false) - nextFlight, dtlsAlert, err := flight13_1Parse(context.Background(), nil, &handshakeContext13{ + cache := dtlsflight.NewCache() + cache.Push(rawHelloRetryRequest, cfg.InitialEpoch, 0, handshake.TypeServerHello, false) + nextFlight, dtlsAlert, err := flight13ParseForTest(t, dtlsflight.Flight13_1, context.Background(), &handshakeContext13{ state: state, cache: cache, cfg: cfg, @@ -766,9 +767,9 @@ func TestFlight13ClientParseAppendsHRRTranscriptOrder(t *testing.T) { }) require.NoError(t, err) require.Nil(t, dtlsAlert) - assert.Equal(t, flight13_3, nextFlight) + assert.Equal(t, dtlsflight.Flight13_3, nextFlight) - clientHello2, dtlsAlert, err := flight13_3Generate(nil, &handshakeContext13{ + clientHello2, dtlsAlert, err := flight13GenerateForTest(t, dtlsflight.Flight13_3, &handshakeContext13{ state: state, cache: cache, cfg: cfg, @@ -777,7 +778,7 @@ func TestFlight13ClientParseAppendsHRRTranscriptOrder(t *testing.T) { require.NoError(t, err) require.Nil(t, dtlsAlert) require.Len(t, clientHello2, 1) - clientHello2Handshake, ok := clientHello2[0].record.Content.(*handshake.Handshake) + clientHello2Handshake, ok := clientHello2[0].Record.Content.(*handshake.Handshake) require.True(t, ok) clientHello2Handshake.Header.MessageSequence = 1 require.NoError(t, appendOutboundHandshakeFlight13(transcript, true, state.CipherSuite, clientHello2)) @@ -797,9 +798,9 @@ func TestFlight13ClientParseAppendsHRRTranscriptOrder(t *testing.T) { ) serverHelloCanonical, err := canonicalHandshake13(rawServerHello) require.NoError(t, err) - cache.push(rawServerHello, cfg.initialEpoch, 1, handshake.TypeServerHello, false) + cache.Push(rawServerHello, cfg.InitialEpoch, 1, handshake.TypeServerHello, false) - nextFlight, dtlsAlert, err = flight13_3Parse(context.Background(), nil, &handshakeContext13{ + nextFlight, dtlsAlert, err = flight13ParseForTest(t, dtlsflight.Flight13_3, context.Background(), &handshakeContext13{ state: state, cache: cache, cfg: cfg, @@ -807,7 +808,7 @@ func TestFlight13ClientParseAppendsHRRTranscriptOrder(t *testing.T) { }) require.NoError(t, err) require.Nil(t, dtlsAlert) - assert.Equal(t, flight13_5, nextFlight) + assert.Equal(t, dtlsflight.Flight13_5, nextFlight) clientHello1Hash := hashTranscript13(clientHello1Canonical) messageHash := canonicalTranscriptHandshake13(handshake.TypeMessageHash, clientHello1Hash) @@ -826,9 +827,9 @@ func TestFlight13_3ParseKeepsReadingWithoutServerHello(t *testing.T) { cfg := testHandshakeConfig13(t) state := &dtlsstate.State{} - nextFlight, dtlsAlert, err := flight13_3Parse(context.Background(), nil, &handshakeContext13{ + nextFlight, dtlsAlert, err := flight13ParseForTest(t, dtlsflight.Flight13_3, context.Background(), &handshakeContext13{ state: state, - cache: newHandshakeCache(), + cache: dtlsflight.NewCache(), cfg: cfg, }) @@ -840,16 +841,16 @@ func TestFlight13_3ParseKeepsReadingWithoutServerHello(t *testing.T) { func TestFlight13_3ParseRejectsSecondHelloRetryRequest(t *testing.T) { cfg := testHandshakeConfig13(t) state := &dtlsstate.State{} - _, _, err := flight13_1Generate(nil, &handshakeContext13{state: state, cfg: cfg}) + _, _, err := flight13GenerateForTest(t, dtlsflight.Flight13_1, &handshakeContext13{state: state, cfg: cfg}) require.NoError(t, err) rawServerHello := marshalHelloRetryRequestServerHello(t, cfg, []extension.Extension{ &extension.SupportedVersions{Versions: []protocol.Version{protocol.Version1_3}, SelectedVersion: true}, }) - cache := newHandshakeCache() - cache.push(rawServerHello, cfg.initialEpoch, 0, handshake.TypeServerHello, false) - nextFlight, dtlsAlert, err := flight13_3Parse(context.Background(), nil, &handshakeContext13{ + cache := dtlsflight.NewCache() + cache.Push(rawServerHello, cfg.InitialEpoch, 0, handshake.TypeServerHello, false) + nextFlight, dtlsAlert, err := flight13ParseForTest(t, dtlsflight.Flight13_3, context.Background(), &handshakeContext13{ state: state, cache: cache, cfg: cfg, @@ -865,14 +866,14 @@ func TestFlight13_3ParseRejectsSecondHelloRetryRequest(t *testing.T) { func TestFlight13_3ParseRejectsWrongLegacyVersion(t *testing.T) { cfg := testHandshakeConfig13(t) state := &dtlsstate.State{} - _, _, err := flight13_1Generate(nil, &handshakeContext13{state: state, cfg: cfg}) + _, _, err := flight13GenerateForTest(t, dtlsflight.Flight13_1, &handshakeContext13{state: state, cfg: cfg}) require.NoError(t, err) - group := cfg.ellipticCurves[0] + group := cfg.EllipticCurves[0] serverKeypair, err := elliptic.GenerateKeypair(group) require.NoError(t, err) - cipherSuiteID := uint16(cfg.localCipherSuites[0].ID()) + cipherSuiteID := uint16(cfg.LocalCipherSuites[0].ID()) serverHello := &handshake.MessageServerHello{ Version: protocol.Version1_0, Random: handshake.Random{RandomBytes: [handshake.RandomBytesLength]byte{0x01}}, @@ -886,9 +887,9 @@ func TestFlight13_3ParseRejectsWrongLegacyVersion(t *testing.T) { rawServerHello, err := (&handshake.Handshake{Message: serverHello}).Marshal() require.NoError(t, err) - cache := newHandshakeCache() - cache.push(rawServerHello, cfg.initialEpoch, 0, handshake.TypeServerHello, false) - nextFlight, dtlsAlert, err := flight13_3Parse(context.Background(), nil, &handshakeContext13{ + cache := dtlsflight.NewCache() + cache.Push(rawServerHello, cfg.InitialEpoch, 0, handshake.TypeServerHello, false) + nextFlight, dtlsAlert, err := flight13ParseForTest(t, dtlsflight.Flight13_3, context.Background(), &handshakeContext13{ state: state, cache: cache, cfg: cfg, @@ -903,10 +904,10 @@ func TestFlight13_3ParseRejectsWrongLegacyVersion(t *testing.T) { func TestFlight13_3ParseRejectsMissingSupportedVersions(t *testing.T) { cfg := testHandshakeConfig13(t) state := &dtlsstate.State{} - _, _, err := flight13_1Generate(nil, &handshakeContext13{state: state, cfg: cfg}) + _, _, err := flight13GenerateForTest(t, dtlsflight.Flight13_1, &handshakeContext13{state: state, cfg: cfg}) require.NoError(t, err) - group := cfg.ellipticCurves[0] + group := cfg.EllipticCurves[0] serverKeypair, err := elliptic.GenerateKeypair(group) require.NoError(t, err) @@ -915,9 +916,9 @@ func TestFlight13_3ParseRejectsMissingSupportedVersions(t *testing.T) { &extension.KeyShare{ServerShare: &extension.KeyShareEntry{Group: group, KeyExchange: serverKeypair.PublicKey}}, }) - cache := newHandshakeCache() - cache.push(rawServerHello, cfg.initialEpoch, 0, handshake.TypeServerHello, false) - nextFlight, dtlsAlert, err := flight13_3Parse(context.Background(), nil, &handshakeContext13{ + cache := dtlsflight.NewCache() + cache.Push(rawServerHello, cfg.InitialEpoch, 0, handshake.TypeServerHello, false) + nextFlight, dtlsAlert, err := flight13ParseForTest(t, dtlsflight.Flight13_3, context.Background(), &handshakeContext13{ state: state, cache: cache, cfg: cfg, @@ -932,7 +933,7 @@ func TestFlight13_3ParseRejectsMissingSupportedVersions(t *testing.T) { func TestFlight13_3ParseRejectsMissingKeyShare(t *testing.T) { cfg := testHandshakeConfig13(t) state := &dtlsstate.State{} - _, _, err := flight13_1Generate(nil, &handshakeContext13{state: state, cfg: cfg}) + _, _, err := flight13GenerateForTest(t, dtlsflight.Flight13_1, &handshakeContext13{state: state, cfg: cfg}) require.NoError(t, err) random := handshake.Random{RandomBytes: [handshake.RandomBytesLength]byte{0x01}} @@ -940,9 +941,9 @@ func TestFlight13_3ParseRejectsMissingKeyShare(t *testing.T) { &extension.SupportedVersions{Versions: []protocol.Version{protocol.Version1_3}, SelectedVersion: true}, }) - cache := newHandshakeCache() - cache.push(rawServerHello, cfg.initialEpoch, 0, handshake.TypeServerHello, false) - nextFlight, dtlsAlert, err := flight13_3Parse(context.Background(), nil, &handshakeContext13{ + cache := dtlsflight.NewCache() + cache.Push(rawServerHello, cfg.InitialEpoch, 0, handshake.TypeServerHello, false) + nextFlight, dtlsAlert, err := flight13ParseForTest(t, dtlsflight.Flight13_3, context.Background(), &handshakeContext13{ state: state, cache: cache, cfg: cfg, @@ -958,7 +959,7 @@ func TestFlight13_3ParseRejectsUnofferedKeyShareGroup(t *testing.T) { cfg := testHandshakeConfig13(t) state := &dtlsstate.State{} - group := cfg.ellipticCurves[0] + group := cfg.EllipticCurves[0] serverKeypair, err := elliptic.GenerateKeypair(group) require.NoError(t, err) @@ -968,9 +969,9 @@ func TestFlight13_3ParseRejectsUnofferedKeyShareGroup(t *testing.T) { &extension.KeyShare{ServerShare: &extension.KeyShareEntry{Group: group, KeyExchange: serverKeypair.PublicKey}}, }) - cache := newHandshakeCache() - cache.push(rawServerHello, cfg.initialEpoch, 0, handshake.TypeServerHello, false) - nextFlight, dtlsAlert, err := flight13_3Parse(context.Background(), nil, &handshakeContext13{ + cache := dtlsflight.NewCache() + cache.Push(rawServerHello, cfg.InitialEpoch, 0, handshake.TypeServerHello, false) + nextFlight, dtlsAlert, err := flight13ParseForTest(t, dtlsflight.Flight13_3, context.Background(), &handshakeContext13{ state: state, cache: cache, cfg: cfg, diff --git a/flighthandlers_server_13_test.go b/flighthandlers_server_13_test.go index 079f55492..0270acc26 100644 --- a/flighthandlers_server_13_test.go +++ b/flighthandlers_server_13_test.go @@ -8,6 +8,7 @@ import ( "testing" dtlserrors "github.com/pion/dtls/v3/internal/errors" + dtlsflight "github.com/pion/dtls/v3/internal/flight" dtlsstate "github.com/pion/dtls/v3/internal/state" "github.com/pion/dtls/v3/pkg/crypto/elliptic" "github.com/pion/dtls/v3/pkg/protocol" @@ -20,7 +21,7 @@ import ( func TestFlight13_0ParseGeneratesKeypairForNegotiatedGroup(t *testing.T) { cfg := testHandshakeConfig13(t) - cfg.ellipticCurves = []elliptic.Curve{elliptic.P384, elliptic.P256} + cfg.EllipticCurves = []elliptic.Curve{elliptic.P384, elliptic.P256} clientKeypair, err := elliptic.GenerateKeypair(elliptic.P384) require.NoError(t, err) @@ -31,12 +32,12 @@ func TestFlight13_0ParseGeneratesKeypairForNegotiatedGroup(t *testing.T) { Version: protocol.Version1_2, Random: handshake.Random{RandomBytes: [handshake.RandomBytesLength]byte{0x01}}, CipherSuiteIDs: []uint16{ - uint16(cfg.localCipherSuites[0].ID()), + uint16(cfg.LocalCipherSuites[0].ID()), }, CompressionMethods: defaultCompressionMethods(), Extensions: []extension.Extension{ &extension.SupportedSignatureAlgorithms{ - SignatureHashAlgorithms: cfg.localSignatureSchemes, + SignatureHashAlgorithms: cfg.LocalSignatureSchemes, }, &extension.SupportedEllipticCurves{ EllipticCurves: []elliptic.Curve{elliptic.P384}, @@ -59,18 +60,19 @@ func TestFlight13_0ParseGeneratesKeypairForNegotiatedGroup(t *testing.T) { NamedCurve: elliptic.X25519, LocalKeypair: staleServerKeypair, } - cache := newHandshakeCache() - cache.push(rawClientHello, cfg.initialEpoch, 0, handshake.TypeClientHello, true) + cache := dtlsflight.NewCache() + cache.Push(rawClientHello, cfg.InitialEpoch, 0, handshake.TypeClientHello, true) - nextFlight, dtlsAlert, err := flight13_0Parse(context.Background(), nil, &handshakeContext13{ - state: state, - cache: cache, - cfg: cfg, - }) + nextFlight, dtlsAlert, err := flight13ParseForTest( + t, dtlsflight.Flight13_0, context.Background(), &handshakeContext13{ + state: state, + cache: cache, + cfg: cfg, + }) require.NoError(t, err) require.Nil(t, dtlsAlert) - assert.Equal(t, flight13_2, nextFlight) + assert.Equal(t, dtlsflight.Flight13_2, nextFlight) require.NotNil(t, state.LocalKeypair) assert.Equal(t, elliptic.P384, state.NamedCurve) assert.Equal(t, elliptic.P384, state.LocalKeypair.Curve) @@ -83,7 +85,7 @@ func TestFlight13_0ParseRejectsClientHelloWithSelectedSupportedVersion(t *testin Version: protocol.Version1_2, Random: handshake.Random{RandomBytes: [handshake.RandomBytesLength]byte{0x01}}, CipherSuiteIDs: []uint16{ - uint16(cfg.localCipherSuites[0].ID()), + uint16(cfg.LocalCipherSuites[0].ID()), }, CompressionMethods: defaultCompressionMethods(), Extensions: []extension.Extension{ @@ -97,14 +99,15 @@ func TestFlight13_0ParseRejectsClientHelloWithSelectedSupportedVersion(t *testin require.NoError(t, err) state := &dtlsstate.State{LocalVersion: protocol.Version1_3} - cache := newHandshakeCache() - cache.push(rawClientHello, cfg.initialEpoch, 0, handshake.TypeClientHello, true) + cache := dtlsflight.NewCache() + cache.Push(rawClientHello, cfg.InitialEpoch, 0, handshake.TypeClientHello, true) - nextFlight, dtlsAlert, err := flight13_0Parse(context.Background(), nil, &handshakeContext13{ - state: state, - cache: cache, - cfg: cfg, - }) + nextFlight, dtlsAlert, err := flight13ParseForTest( + t, dtlsflight.Flight13_0, context.Background(), &handshakeContext13{ + state: state, + cache: cache, + cfg: cfg, + }) require.ErrorIs(t, err, dtlserrors.ErrInvalidClientHello) require.NotNil(t, dtlsAlert) @@ -116,7 +119,7 @@ func TestFlight13_0ParseRejectsClientHelloWithSelectedSupportedVersion(t *testin func pushFlight13_0ClientHello( t *testing.T, - cache *handshakeCache, + cache *dtlsflight.Cache, cfg *handshakeConfig, exts []extension.Extension, ) []byte { @@ -126,7 +129,7 @@ func pushFlight13_0ClientHello( Version: protocol.Version1_2, Random: handshake.Random{RandomBytes: [handshake.RandomBytesLength]byte{0x01}}, CipherSuiteIDs: []uint16{ - uint16(cfg.localCipherSuites[0].ID()), + uint16(cfg.LocalCipherSuites[0].ID()), }, CompressionMethods: defaultCompressionMethods(), Extensions: exts, @@ -134,7 +137,7 @@ func pushFlight13_0ClientHello( rawClientHello, err := (&handshake.Handshake{Message: clientHello}).Marshal() require.NoError(t, err) - cache.push(rawClientHello, cfg.initialEpoch, 0, handshake.TypeClientHello, true) + cache.Push(rawClientHello, cfg.InitialEpoch, 0, handshake.TypeClientHello, true) return rawClientHello } @@ -142,15 +145,15 @@ func pushFlight13_0ClientHello( func requiredClientHello13Extensions(t *testing.T, cfg *handshakeConfig) []extension.Extension { t.Helper() - clientKeypair, err := elliptic.GenerateKeypair(cfg.ellipticCurves[0]) + clientKeypair, err := elliptic.GenerateKeypair(cfg.EllipticCurves[0]) require.NoError(t, err) return []extension.Extension{ &extension.SupportedSignatureAlgorithms{ - SignatureHashAlgorithms: cfg.localSignatureSchemes, + SignatureHashAlgorithms: cfg.LocalSignatureSchemes, }, &extension.SupportedEllipticCurves{ - EllipticCurves: cfg.ellipticCurves, + EllipticCurves: cfg.EllipticCurves, }, &extension.KeyShare{ ClientShares: []extension.KeyShareEntry{ @@ -167,26 +170,27 @@ func TestFlight13_0ParseRequiresCertificateAuthClientHelloExtensions(t *testing. t.Run("AcceptsSignatureAlgorithmsAndSupportedGroups", func(t *testing.T) { cfg := testHandshakeConfig13(t) state := &dtlsstate.State{LocalVersion: protocol.Version1_3} - cache := newHandshakeCache() + cache := dtlsflight.NewCache() pushFlight13_0ClientHello(t, cache, cfg, requiredClientHello13Extensions(t, cfg)) - nextFlight, dtlsAlert, err := flight13_0Parse(context.Background(), nil, &handshakeContext13{ - state: state, - cache: cache, - cfg: cfg, - }) + nextFlight, dtlsAlert, err := flight13ParseForTest( + t, dtlsflight.Flight13_0, context.Background(), &handshakeContext13{ + state: state, + cache: cache, + cfg: cfg, + }) require.NoError(t, err) require.Nil(t, dtlsAlert) - assert.Equal(t, flight13_2, nextFlight) - assert.Equal(t, cfg.localSignatureSchemes, state.RemoteSignatureSchemes) - assert.Equal(t, cfg.ellipticCurves, state.RemoteGroups) + assert.Equal(t, dtlsflight.Flight13_2, nextFlight) + assert.Equal(t, cfg.LocalSignatureSchemes, state.RemoteSignatureSchemes) + assert.Equal(t, cfg.EllipticCurves, state.RemoteGroups) }) t.Run("AllowsPreSharedKeyWithoutCertificateAuthExtensions", func(t *testing.T) { cfg := testHandshakeConfig13(t) state := &dtlsstate.State{LocalVersion: protocol.Version1_3} - cache := newHandshakeCache() + cache := dtlsflight.NewCache() binder := make([]byte, 32) pushFlight13_0ClientHello(t, cache, cfg, []extension.Extension{ &extension.SupportedVersions{ @@ -200,29 +204,31 @@ func TestFlight13_0ParseRequiresCertificateAuthClientHelloExtensions(t *testing. }, }) - nextFlight, dtlsAlert, err := flight13_0Parse(context.Background(), nil, &handshakeContext13{ - state: state, - cache: cache, - cfg: cfg, - }) + nextFlight, dtlsAlert, err := flight13ParseForTest( + t, dtlsflight.Flight13_0, context.Background(), &handshakeContext13{ + state: state, + cache: cache, + cfg: cfg, + }) require.NoError(t, err) require.Nil(t, dtlsAlert) - assert.Equal(t, flight13_2, nextFlight) + assert.Equal(t, dtlsflight.Flight13_2, nextFlight) }) t.Run("RejectsMissingSignatureAlgorithms", func(t *testing.T) { cfg := testHandshakeConfig13(t) state := &dtlsstate.State{LocalVersion: protocol.Version1_3} - cache := newHandshakeCache() + cache := dtlsflight.NewCache() exts := requiredClientHello13Extensions(t, cfg)[1:] pushFlight13_0ClientHello(t, cache, cfg, exts) - nextFlight, dtlsAlert, err := flight13_0Parse(context.Background(), nil, &handshakeContext13{ - state: state, - cache: cache, - cfg: cfg, - }) + nextFlight, dtlsAlert, err := flight13ParseForTest( + t, dtlsflight.Flight13_0, context.Background(), &handshakeContext13{ + state: state, + cache: cache, + cfg: cfg, + }) require.ErrorIs(t, err, dtlserrors.ErrMissingClientHelloExtension) require.NotNil(t, dtlsAlert) @@ -233,20 +239,21 @@ func TestFlight13_0ParseRequiresCertificateAuthClientHelloExtensions(t *testing. t.Run("RejectsSignatureAlgorithmsCertAsSubstitute", func(t *testing.T) { cfg := testHandshakeConfig13(t) state := &dtlsstate.State{LocalVersion: protocol.Version1_3} - cache := newHandshakeCache() + cache := dtlsflight.NewCache() exts := requiredClientHello13Extensions(t, cfg)[1:] exts = append([]extension.Extension{ &extension.SignatureAlgorithmsCert{ - SignatureHashAlgorithms: cfg.localSignatureSchemes, + SignatureHashAlgorithms: cfg.LocalSignatureSchemes, }, }, exts...) pushFlight13_0ClientHello(t, cache, cfg, exts) - nextFlight, dtlsAlert, err := flight13_0Parse(context.Background(), nil, &handshakeContext13{ - state: state, - cache: cache, - cfg: cfg, - }) + nextFlight, dtlsAlert, err := flight13ParseForTest( + t, dtlsflight.Flight13_0, context.Background(), &handshakeContext13{ + state: state, + cache: cache, + cfg: cfg, + }) require.ErrorIs(t, err, dtlserrors.ErrMissingClientHelloExtension) require.NotNil(t, dtlsAlert) @@ -257,16 +264,17 @@ func TestFlight13_0ParseRequiresCertificateAuthClientHelloExtensions(t *testing. t.Run("RejectsMissingSupportedGroups", func(t *testing.T) { cfg := testHandshakeConfig13(t) state := &dtlsstate.State{LocalVersion: protocol.Version1_3} - cache := newHandshakeCache() + cache := dtlsflight.NewCache() required := requiredClientHello13Extensions(t, cfg) exts := []extension.Extension{required[0], required[2], required[3]} pushFlight13_0ClientHello(t, cache, cfg, exts) - nextFlight, dtlsAlert, err := flight13_0Parse(context.Background(), nil, &handshakeContext13{ - state: state, - cache: cache, - cfg: cfg, - }) + nextFlight, dtlsAlert, err := flight13ParseForTest( + t, dtlsflight.Flight13_0, context.Background(), &handshakeContext13{ + state: state, + cache: cache, + cfg: cfg, + }) require.ErrorIs(t, err, dtlserrors.ErrMissingClientHelloExtension) require.NotNil(t, dtlsAlert) @@ -277,24 +285,25 @@ func TestFlight13_0ParseRequiresCertificateAuthClientHelloExtensions(t *testing. func TestFlight13ServerParseAppendsNoHRRTranscriptOrder(t *testing.T) { cfg := testHandshakeConfig13(t) - cfg.insecureSkipHelloVerify = true + cfg.InsecureSkipHelloVerify = true state := &dtlsstate.State{LocalVersion: protocol.Version1_3} - cache := newHandshakeCache() + cache := dtlsflight.NewCache() rawClientHello := pushFlight13_0ClientHello(t, cache, cfg, requiredClientHello13Extensions(t, cfg)) clientHelloCanonical, err := canonicalHandshake13(rawClientHello) require.NoError(t, err) transcript := newHandshakeTranscript13() - nextFlight, dtlsAlert, err := flight13_0Parse(context.Background(), nil, &handshakeContext13{ - state: state, - cache: cache, - cfg: cfg, - transcript: transcript, - }) + nextFlight, dtlsAlert, err := flight13ParseForTest( + t, dtlsflight.Flight13_0, context.Background(), &handshakeContext13{ + state: state, + cache: cache, + cfg: cfg, + transcript: transcript, + }) require.NoError(t, err) require.Nil(t, dtlsAlert) - assert.Equal(t, flight13_4, nextFlight) + assert.Equal(t, dtlsflight.Flight13_4, nextFlight) assert.Equal(t, []transcriptMessage13{ {id: transcriptMessageID13{sender: transcriptClient13, seq: 0}, typ: handshake.TypeClientHello}, }, transcript.order) @@ -308,7 +317,7 @@ func TestFlight13ServerParseAppendsHRRTranscriptOrder(t *testing.T) { LocalVersion: protocol.Version1_3, Cookie: cookie, } - cache := newHandshakeCache() + cache := dtlsflight.NewCache() rawClientHello1 := pushFlight13_0ClientHello(t, cache, cfg, requiredClientHello13Extensions(t, cfg)) clientHello1Canonical, err := canonicalHandshake13(rawClientHello1) require.NoError(t, err) @@ -320,12 +329,12 @@ func TestFlight13ServerParseAppendsHRRTranscriptOrder(t *testing.T) { transcript: transcript, } - nextFlight, dtlsAlert, err := flight13_0Parse(context.Background(), nil, flightCtx) + nextFlight, dtlsAlert, err := flight13ParseForTest(t, dtlsflight.Flight13_0, context.Background(), flightCtx) require.NoError(t, err) require.Nil(t, dtlsAlert) - assert.Equal(t, flight13_2, nextFlight) + assert.Equal(t, dtlsflight.Flight13_2, nextFlight) - helloRetryRequest, dtlsAlert, err := flight13_2Generate(nil, flightCtx) + helloRetryRequest, dtlsAlert, err := flight13GenerateForTest(t, dtlsflight.Flight13_2, flightCtx) require.NoError(t, err) require.Nil(t, dtlsAlert) require.Len(t, helloRetryRequest, 1) @@ -337,10 +346,10 @@ func TestFlight13ServerParseAppendsHRRTranscriptOrder(t *testing.T) { clientHello2Canonical, err := canonicalHandshake13(rawClientHello2) require.NoError(t, err) - nextFlight, dtlsAlert, err = flight13_2Parse(context.Background(), nil, flightCtx) + nextFlight, dtlsAlert, err = flight13ParseForTest(t, dtlsflight.Flight13_2, context.Background(), flightCtx) require.NoError(t, err) require.Nil(t, dtlsAlert) - assert.Equal(t, flight13_4, nextFlight) + assert.Equal(t, dtlsflight.Flight13_4, nextFlight) clientHello1Hash := hashTranscript13(clientHello1Canonical) messageHash := canonicalTranscriptHandshake13(handshake.TypeMessageHash, clientHello1Hash) @@ -360,17 +369,19 @@ func serverHelloFromFlight13_2( t.Helper() if state.CipherSuite == nil { - state.CipherSuite = cfg.localCipherSuites[0] + state.CipherSuite = cfg.LocalCipherSuites[0] } - pkts, dtlsAlert, err := flight13_2Generate(nil, flight13_2Context(state, newHandshakeCache(), cfg)) + pkts, dtlsAlert, err := flight13GenerateForTest( + t, dtlsflight.Flight13_2, flight13_2Context(state, dtlsflight.NewCache(), cfg), + ) require.NoError(t, err) require.Nil(t, dtlsAlert) require.Len(t, pkts, 1) - require.NotNil(t, pkts[0].record) - assert.Equal(t, protocol.Version1_2, pkts[0].record.Header.Version) + require.NotNil(t, pkts[0].Record) + assert.Equal(t, protocol.Version1_2, pkts[0].Record.Header.Version) - content, ok := pkts[0].record.Content.(*handshake.Handshake) + content, ok := pkts[0].Record.Content.(*handshake.Handshake) require.True(t, ok) serverHello, ok := content.Message.(*handshake.MessageServerHello) @@ -424,11 +435,13 @@ func TestFlight13_2Generate(t *testing.T) { cfg := testHandshakeConfig13(t) state := &dtlsstate.State{ LocalVersion: protocol.Version1_3, - CipherSuite: cfg.localCipherSuites[0], + CipherSuite: cfg.LocalCipherSuites[0], HandshakeSendSequence: 7, } - _, dtlsAlert, err := flight13_2Generate(nil, flight13_2Context(state, newHandshakeCache(), cfg)) + _, dtlsAlert, err := flight13GenerateForTest( + t, dtlsflight.Flight13_2, flight13_2Context(state, dtlsflight.NewCache(), cfg), + ) require.NoError(t, err) require.Nil(t, dtlsAlert) @@ -439,7 +452,9 @@ func TestFlight13_2Generate(t *testing.T) { state := &dtlsstate.State{LocalVersion: protocol.Version1_3} cfg := testHandshakeConfig13(t) - pkts, dtlsAlert, err := flight13_2Generate(nil, flight13_2Context(state, newHandshakeCache(), cfg)) + pkts, dtlsAlert, err := flight13GenerateForTest( + t, dtlsflight.Flight13_2, flight13_2Context(state, dtlsflight.NewCache(), cfg), + ) require.ErrorIs(t, err, dtlserrors.ErrCipherSuiteUnset) require.Nil(t, dtlsAlert) require.Nil(t, pkts) @@ -464,7 +479,7 @@ func TestFlight13_2Generate(t *testing.T) { serverHello := serverHelloFromFlight13_2(t, state, cfg) require.NotNil(t, serverHello.CipherSuiteID) - assert.Equal(t, uint16(cfg.localCipherSuites[0].ID()), *serverHello.CipherSuiteID) + assert.Equal(t, uint16(cfg.LocalCipherSuites[0].ID()), *serverHello.CipherSuiteID) require.NotNil(t, serverHello.CompressionMethod) assert.Equal(t, defaultCompressionMethods()[0], serverHello.CompressionMethod) @@ -529,7 +544,7 @@ func TestFlight13_2Generate(t *testing.T) { supportedVersions, ok := findSupportedVersions(serverHello.Extensions) require.True(t, ok) - assert.Equal(t, supportedVersionsRange(cfg.minVersion, cfg.maxVersion), supportedVersions.Versions) + assert.Equal(t, supportedVersionsRange(cfg.MinVersion, cfg.MaxVersion), supportedVersions.Versions) keyShare, ok := findKeyShare(serverHello.Extensions) require.True(t, ok) @@ -544,7 +559,7 @@ func TestFlight13_2Generate(t *testing.T) { func pushClientHello13( t *testing.T, - cache *handshakeCache, + cache *dtlsflight.Cache, version protocol.Version, exts []extension.Extension, ) { @@ -555,7 +570,7 @@ func pushClientHello13( func pushClientHello13WithSequence( t *testing.T, - cache *handshakeCache, + cache *dtlsflight.Cache, version protocol.Version, seq uint16, exts []extension.Extension, @@ -576,12 +591,12 @@ func pushClientHello13WithSequence( raw, err := content.Marshal() require.NoError(t, err) - cache.push(raw, 0, seq, handshake.TypeClientHello, true) + cache.Push(raw, 0, seq, handshake.TypeClientHello, true) return raw } -func flight13_2Context(state *dtlsstate.State, cache *handshakeCache, cfg *handshakeConfig) *handshakeContext13 { +func flight13_2Context(state *dtlsstate.State, cache *dtlsflight.Cache, cfg *handshakeConfig) *handshakeContext13 { return &handshakeContext13{ state: state, cache: cache, @@ -595,43 +610,49 @@ func TestFlight13_2Parse(t *testing.T) { t.Run("AdvancesToFlight4OnMatchingCookie", func(t *testing.T) { state := &dtlsstate.State{LocalVersion: protocol.Version1_3, Cookie: cookie} - cache := newHandshakeCache() + cache := dtlsflight.NewCache() cfg := testHandshakeConfig13(t) exts := append(requiredClientHello13Extensions(t, cfg), &extension.CookieExt{Cookie: cookie}) pushClientHello13(t, cache, protocol.Version1_2, exts) - next, dtlsAlert, err := flight13_2Parse(context.Background(), nil, flight13_2Context(state, cache, cfg)) + next, dtlsAlert, err := flight13ParseForTest( + t, dtlsflight.Flight13_2, context.Background(), flight13_2Context(state, cache, cfg), + ) require.NoError(t, err) require.Nil(t, dtlsAlert) - assert.Equal(t, flight13_4, next) + assert.Equal(t, dtlsflight.Flight13_4, next) assert.Equal(t, 1, state.HandshakeRecvSequence) }) t.Run("KeepsWaitingWhenNoClientHelloCached", func(t *testing.T) { state := &dtlsstate.State{LocalVersion: protocol.Version1_3, Cookie: cookie} - cache := newHandshakeCache() + cache := dtlsflight.NewCache() cfg := testHandshakeConfig13(t) - next, dtlsAlert, err := flight13_2Parse(context.Background(), nil, flight13_2Context(state, cache, cfg)) + next, dtlsAlert, err := flight13ParseForTest( + t, dtlsflight.Flight13_2, context.Background(), flight13_2Context(state, cache, cfg), + ) require.NoError(t, err) require.Nil(t, dtlsAlert) - assert.Equal(t, flightVal13(0), next) + assert.Equal(t, dtlsflight.Flight13(0), next) assert.Equal(t, 0, state.HandshakeRecvSequence) }) t.Run("KeepsWaitingWhenCookieNotYetEchoed", func(t *testing.T) { state := &dtlsstate.State{LocalVersion: protocol.Version1_3, Cookie: cookie, ServerName: "original.example"} - cache := newHandshakeCache() + cache := dtlsflight.NewCache() cfg := testHandshakeConfig13(t) exts := append(requiredClientHello13Extensions(t, cfg), &extension.ServerName{ServerName: "poison.example"}) pushClientHello13(t, cache, protocol.Version1_2, exts) - next, dtlsAlert, err := flight13_2Parse(context.Background(), nil, flight13_2Context(state, cache, cfg)) + next, dtlsAlert, err := flight13ParseForTest( + t, dtlsflight.Flight13_2, context.Background(), flight13_2Context(state, cache, cfg), + ) require.NoError(t, err) require.Nil(t, dtlsAlert) - assert.Equal(t, flightVal13(0), next) + assert.Equal(t, dtlsflight.Flight13(0), next) assert.Equal(t, 0, state.HandshakeRecvSequence) assert.Equal(t, "original.example", state.ServerName) assert.Empty(t, state.RemoteSignatureSchemes) @@ -640,16 +661,18 @@ func TestFlight13_2Parse(t *testing.T) { t.Run("RejectsCookieMismatch", func(t *testing.T) { state := &dtlsstate.State{LocalVersion: protocol.Version1_3, Cookie: cookie, ServerName: "original.example"} - cache := newHandshakeCache() + cache := dtlsflight.NewCache() cfg := testHandshakeConfig13(t) exts := append(requiredClientHello13Extensions(t, cfg), &extension.ServerName{ServerName: "poison.example"}, &extension.CookieExt{Cookie: []byte{0x00, 0x01, 0x02, 0x03}}) pushClientHello13(t, cache, protocol.Version1_2, exts) - next, dtlsAlert, err := flight13_2Parse(context.Background(), nil, flight13_2Context(state, cache, cfg)) + next, dtlsAlert, err := flight13ParseForTest( + t, dtlsflight.Flight13_2, context.Background(), flight13_2Context(state, cache, cfg), + ) require.ErrorIs(t, err, dtlserrors.ErrCookieMismatch) - assert.Equal(t, flightVal13(0), next) + assert.Equal(t, dtlsflight.Flight13(0), next) require.NotNil(t, dtlsAlert) assert.Equal(t, &alert.Alert{Level: alert.Fatal, Description: alert.AccessDenied}, dtlsAlert) assert.Equal(t, 0, state.HandshakeRecvSequence) @@ -660,32 +683,36 @@ func TestFlight13_2Parse(t *testing.T) { t.Run("RejectsUnsupportedVersion", func(t *testing.T) { state := &dtlsstate.State{LocalVersion: protocol.Version1_3, Cookie: cookie} - cache := newHandshakeCache() + cache := dtlsflight.NewCache() cfg := testHandshakeConfig13(t) pushClientHello13(t, cache, protocol.Version{Major: 0xfe, Minor: 0xfd - 1}, []extension.Extension{ &extension.CookieExt{Cookie: cookie}, }) - next, dtlsAlert, err := flight13_2Parse(context.Background(), nil, flight13_2Context(state, cache, cfg)) + next, dtlsAlert, err := flight13ParseForTest( + t, dtlsflight.Flight13_2, context.Background(), flight13_2Context(state, cache, cfg), + ) require.ErrorIs(t, err, dtlserrors.ErrUnsupportedProtocolVersion) - assert.Equal(t, flightVal13(0), next) + assert.Equal(t, dtlsflight.Flight13(0), next) require.NotNil(t, dtlsAlert) assert.Equal(t, &alert.Alert{Level: alert.Fatal, Description: alert.ProtocolVersion}, dtlsAlert) }) t.Run("RejectsMissingCertificateAuthExtensions", func(t *testing.T) { state := &dtlsstate.State{LocalVersion: protocol.Version1_3, Cookie: cookie} - cache := newHandshakeCache() + cache := dtlsflight.NewCache() cfg := testHandshakeConfig13(t) pushClientHello13(t, cache, protocol.Version1_2, []extension.Extension{ &extension.CookieExt{Cookie: cookie}, }) - next, dtlsAlert, err := flight13_2Parse(context.Background(), nil, flight13_2Context(state, cache, cfg)) + next, dtlsAlert, err := flight13ParseForTest( + t, dtlsflight.Flight13_2, context.Background(), flight13_2Context(state, cache, cfg), + ) require.ErrorIs(t, err, dtlserrors.ErrMissingClientHelloExtension) - assert.Equal(t, flightVal13(0), next) + assert.Equal(t, dtlsflight.Flight13(0), next) require.NotNil(t, dtlsAlert) assert.Equal(t, &alert.Alert{Level: alert.Fatal, Description: alert.MissingExtension}, dtlsAlert) }) diff --git a/handshake_cache.go b/handshake_cache.go deleted file mode 100644 index 55cfbbf66..000000000 --- a/handshake_cache.go +++ /dev/null @@ -1,235 +0,0 @@ -// SPDX-FileCopyrightText: 2026 The Pion community -// SPDX-License-Identifier: MIT - -package dtls - -import ( - "sync" - - "github.com/pion/dtls/v3/pkg/crypto/prf" - "github.com/pion/dtls/v3/pkg/protocol/handshake" -) - -type handshakeCacheItem struct { - typ handshake.Type - isClient bool - epoch uint16 - messageSequence uint16 - data []byte -} - -type handshakeCachePullRule struct { - typ handshake.Type - epoch uint16 - isClient bool - optional bool -} - -type handshakeCache struct { - cache []*handshakeCacheItem - mu sync.Mutex -} - -func newHandshakeCache() *handshakeCache { - return &handshakeCache{} -} - -func (h *handshakeCache) push(data []byte, epoch, messageSequence uint16, typ handshake.Type, isClient bool) { - h.mu.Lock() - defer h.mu.Unlock() - - h.cache = append(h.cache, &handshakeCacheItem{ - data: data, - epoch: epoch, - messageSequence: messageSequence, - typ: typ, - isClient: isClient, - }) -} - -// returns a list handshakes that match the requested rules -// the list will contain null entries for rules that can't be satisfied -// multiple entries may match a rule, but only the last match is returned (ie ClientHello with cookies). -func (h *handshakeCache) pull(rules ...handshakeCachePullRule) []*handshakeCacheItem { - h.mu.Lock() - defer h.mu.Unlock() - - out := make([]*handshakeCacheItem, len(rules)) - for i, r := range rules { - for _, c := range h.cache { - if c.typ == r.typ && c.isClient == r.isClient && c.epoch == r.epoch { - switch { - case out[i] == nil: - out[i] = c - case out[i].messageSequence < c.messageSequence: - out[i] = c - } - } - } - } - - return out -} - -// fullPullMap pulls all handshakes between rules[0] to rules[len(rules)-1] as map. -func (h *handshakeCache) fullPullMap( - startSeq int, - cipherSuite CipherSuite, - rules ...handshakeCachePullRule, -) (int, map[handshake.Type]handshake.Message, bool) { - seq, msgs, _, ok := h.fullPullMapItems(startSeq, cipherSuite, rules...) - - return seq, msgs, ok -} - -func (h *handshakeCache) fullPullMapItems( - startSeq int, - cipherSuite CipherSuite, - rules ...handshakeCachePullRule, -) (int, map[handshake.Type]handshake.Message, []*handshakeCacheItem, bool) { - h.mu.Lock() - defer h.mu.Unlock() - - ci, ok := h.pullLastCacheItems(rules) - if !ok { - return startSeq, nil, nil, false - } - - return fullPullMapCacheItems(startSeq, cipherSuite, rules, ci) -} - -func (h *handshakeCache) pullLastCacheItems(rules []handshakeCachePullRule) ([]*handshakeCacheItem, bool) { - items := make([]*handshakeCacheItem, len(rules)) - for i, rule := range rules { - items[i] = h.lastCacheItemForRule(rule) - if !rule.optional && items[i] == nil { - return nil, false - } - } - - return items, true -} - -func (h *handshakeCache) lastCacheItemForRule(rule handshakeCachePullRule) *handshakeCacheItem { - var last *handshakeCacheItem - for _, c := range h.cache { - if !cacheItemMatchesRule(c, rule) { - continue - } - if last == nil || last.messageSequence < c.messageSequence { - last = c - } - } - - return last -} - -func cacheItemMatchesRule(item *handshakeCacheItem, rule handshakeCachePullRule) bool { - return item.typ == rule.typ && item.isClient == rule.isClient && item.epoch == rule.epoch -} - -func fullPullMapCacheItems( - startSeq int, - cipherSuite CipherSuite, - rules []handshakeCachePullRule, - ci []*handshakeCacheItem, -) (int, map[handshake.Type]handshake.Message, []*handshakeCacheItem, bool) { - out := make(map[handshake.Type]handshake.Message) - items := make([]*handshakeCacheItem, 0, len(rules)) - seq := startSeq - keyExchangeAlgorithm := keyExchangeAlgorithmForCipherSuite(cipherSuite) - for i, r := range rules { - typ := r.typ - item := ci[i] - if item == nil { - continue - } - rawHandshake, ok := unmarshalCachedHandshake(item, keyExchangeAlgorithm) - if !ok { - return startSeq, nil, nil, false - } - if uint16(seq) != rawHandshake.Header.MessageSequence { //nolint:gosec // G115 - // There is a gap. Some messages are not arrived. - return startSeq, nil, nil, false - } - seq++ - out[typ] = rawHandshake.Message - items = append(items, item) - } - if len(items) == 0 { - return seq, nil, nil, false - } - - return seq, out, items, true -} - -func keyExchangeAlgorithmForCipherSuite(cipherSuite CipherSuite) CipherSuiteKeyExchangeAlgorithm { - if cipherSuite == nil { - return 0 - } - - return cipherSuite.KeyExchangeAlgorithm() -} - -func unmarshalCachedHandshake( - item *handshakeCacheItem, - keyExchangeAlgorithm CipherSuiteKeyExchangeAlgorithm, -) (*handshake.Handshake, bool) { - rawHandshake := &handshake.Handshake{ - KeyExchangeAlgorithm: keyExchangeAlgorithm, - } - if err := rawHandshake.Unmarshal(item.data); err != nil { - return nil, false - } - - return rawHandshake, true -} - -// pullAndMerge calls pull and then merges the results, ignoring any null entries. -func (h *handshakeCache) pullAndMerge(rules ...handshakeCachePullRule) []byte { - merged := []byte{} - - for _, p := range h.pull(rules...) { - if p != nil { - merged = append(merged, p.data...) - } - } - - return merged -} - -// sessionHash returns the session hash for Extended Master Secret support -// https://tools.ietf.org/html/draft-ietf-tls-session-hash-06#section-4 -func (h *handshakeCache) sessionHash(hf prf.HashFunc, epoch uint16, additional ...[]byte) ([]byte, error) { - merged := []byte{} - - // Order defined by https://tools.ietf.org/html/rfc5246#section-7.3 - handshakeBuffer := h.pull( - handshakeCachePullRule{handshake.TypeClientHello, epoch, true, false}, - handshakeCachePullRule{handshake.TypeServerHello, epoch, false, false}, - handshakeCachePullRule{handshake.TypeCertificate, epoch, false, false}, - handshakeCachePullRule{handshake.TypeServerKeyExchange, epoch, false, false}, - handshakeCachePullRule{handshake.TypeCertificateRequest, epoch, false, false}, - handshakeCachePullRule{handshake.TypeServerHelloDone, epoch, false, false}, - handshakeCachePullRule{handshake.TypeCertificate, epoch, true, false}, - handshakeCachePullRule{handshake.TypeClientKeyExchange, epoch, true, false}, - ) - - for _, p := range handshakeBuffer { - if p == nil { - continue - } - - merged = append(merged, p.data...) - } - for _, a := range additional { - merged = append(merged, a...) - } - - hash := hf() - if _, err := hash.Write(merged); err != nil { - return []byte{}, err - } - - return hash.Sum(nil), nil -} diff --git a/handshake_cache_test.go b/handshake_cache_test.go index 565baf22b..d1425f38d 100644 --- a/handshake_cache_test.go +++ b/handshake_cache_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/pion/dtls/v3/internal/ciphersuite" + dtlsflight "github.com/pion/dtls/v3/internal/flight" "github.com/pion/dtls/v3/pkg/protocol" "github.com/pion/dtls/v3/pkg/protocol/handshake" "github.com/stretchr/testify/assert" @@ -16,113 +17,113 @@ import ( func TestHandshakeCacheSinglePush(t *testing.T) { for _, test := range []struct { Name string - Rule []handshakeCachePullRule - Input []handshakeCacheItem + Rule []dtlsflight.HandshakeCachePullRule + Input []dtlsflight.HandshakeCacheItem Expected []byte }{ { Name: "Single Push", - Input: []handshakeCacheItem{ - {0, true, 0, 0, []byte{0x00}}, + Input: []dtlsflight.HandshakeCacheItem{ + {Typ: 0, IsClient: true, Epoch: 0, MessageSequence: 0, Data: []byte{0x00}}, }, - Rule: []handshakeCachePullRule{ - {0, 0, true, false}, + Rule: []dtlsflight.HandshakeCachePullRule{ + {Typ: 0, Epoch: 0, IsClient: true, Optional: false}, }, Expected: []byte{0x00}, }, { Name: "Multi Push", - Input: []handshakeCacheItem{ - {0, true, 0, 0, []byte{0x00}}, - {1, true, 0, 1, []byte{0x01}}, - {2, true, 0, 2, []byte{0x02}}, + Input: []dtlsflight.HandshakeCacheItem{ + {Typ: 0, IsClient: true, Epoch: 0, MessageSequence: 0, Data: []byte{0x00}}, + {Typ: 1, IsClient: true, Epoch: 0, MessageSequence: 1, Data: []byte{0x01}}, + {Typ: 2, IsClient: true, Epoch: 0, MessageSequence: 2, Data: []byte{0x02}}, }, - Rule: []handshakeCachePullRule{ - {0, 0, true, false}, - {1, 0, true, false}, - {2, 0, true, false}, + Rule: []dtlsflight.HandshakeCachePullRule{ + {Typ: 0, Epoch: 0, IsClient: true, Optional: false}, + {Typ: 1, Epoch: 0, IsClient: true, Optional: false}, + {Typ: 2, Epoch: 0, IsClient: true, Optional: false}, }, Expected: []byte{0x00, 0x01, 0x02}, }, { Name: "Multi Push, Rules set order", - Input: []handshakeCacheItem{ - {2, true, 0, 2, []byte{0x02}}, - {0, true, 0, 0, []byte{0x00}}, - {1, true, 0, 1, []byte{0x01}}, + Input: []dtlsflight.HandshakeCacheItem{ + {Typ: 2, IsClient: true, Epoch: 0, MessageSequence: 2, Data: []byte{0x02}}, + {Typ: 0, IsClient: true, Epoch: 0, MessageSequence: 0, Data: []byte{0x00}}, + {Typ: 1, IsClient: true, Epoch: 0, MessageSequence: 1, Data: []byte{0x01}}, }, - Rule: []handshakeCachePullRule{ - {0, 0, true, false}, - {1, 0, true, false}, - {2, 0, true, false}, + Rule: []dtlsflight.HandshakeCachePullRule{ + {Typ: 0, Epoch: 0, IsClient: true, Optional: false}, + {Typ: 1, Epoch: 0, IsClient: true, Optional: false}, + {Typ: 2, Epoch: 0, IsClient: true, Optional: false}, }, Expected: []byte{0x00, 0x01, 0x02}, }, { Name: "Multi Push, Dupe Seqnum", - Input: []handshakeCacheItem{ - {0, true, 0, 0, []byte{0x00}}, - {1, true, 0, 1, []byte{0x01}}, - {1, true, 0, 1, []byte{0x01}}, + Input: []dtlsflight.HandshakeCacheItem{ + {Typ: 0, IsClient: true, Epoch: 0, MessageSequence: 0, Data: []byte{0x00}}, + {Typ: 1, IsClient: true, Epoch: 0, MessageSequence: 1, Data: []byte{0x01}}, + {Typ: 1, IsClient: true, Epoch: 0, MessageSequence: 1, Data: []byte{0x01}}, }, - Rule: []handshakeCachePullRule{ - {0, 0, true, false}, - {1, 0, true, false}, + Rule: []dtlsflight.HandshakeCachePullRule{ + {Typ: 0, Epoch: 0, IsClient: true, Optional: false}, + {Typ: 1, Epoch: 0, IsClient: true, Optional: false}, }, Expected: []byte{0x00, 0x01}, }, { Name: "Multi Push, Dupe Seqnum Client/Server", - Input: []handshakeCacheItem{ - {0, true, 0, 0, []byte{0x00}}, - {1, true, 0, 1, []byte{0x01}}, - {1, false, 0, 1, []byte{0x02}}, + Input: []dtlsflight.HandshakeCacheItem{ + {Typ: 0, IsClient: true, Epoch: 0, MessageSequence: 0, Data: []byte{0x00}}, + {Typ: 1, IsClient: true, Epoch: 0, MessageSequence: 1, Data: []byte{0x01}}, + {Typ: 1, IsClient: false, Epoch: 0, MessageSequence: 1, Data: []byte{0x02}}, }, - Rule: []handshakeCachePullRule{ - {0, 0, true, false}, - {1, 0, true, false}, - {1, 0, false, false}, + Rule: []dtlsflight.HandshakeCachePullRule{ + {Typ: 0, Epoch: 0, IsClient: true, Optional: false}, + {Typ: 1, Epoch: 0, IsClient: true, Optional: false}, + {Typ: 1, Epoch: 0, IsClient: false, Optional: false}, }, Expected: []byte{0x00, 0x01, 0x02}, }, { Name: "Multi Push, Dupe Seqnum with Unique HandshakeType", - Input: []handshakeCacheItem{ - {1, true, 0, 0, []byte{0x00}}, - {2, true, 0, 1, []byte{0x01}}, - {3, false, 0, 0, []byte{0x02}}, + Input: []dtlsflight.HandshakeCacheItem{ + {Typ: 1, IsClient: true, Epoch: 0, MessageSequence: 0, Data: []byte{0x00}}, + {Typ: 2, IsClient: true, Epoch: 0, MessageSequence: 1, Data: []byte{0x01}}, + {Typ: 3, IsClient: false, Epoch: 0, MessageSequence: 0, Data: []byte{0x02}}, }, - Rule: []handshakeCachePullRule{ - {1, 0, true, false}, - {2, 0, true, false}, - {3, 0, false, false}, + Rule: []dtlsflight.HandshakeCachePullRule{ + {Typ: 1, Epoch: 0, IsClient: true, Optional: false}, + {Typ: 2, Epoch: 0, IsClient: true, Optional: false}, + {Typ: 3, Epoch: 0, IsClient: false, Optional: false}, }, Expected: []byte{0x00, 0x01, 0x02}, }, { Name: "Multi Push, Wrong epoch", - Input: []handshakeCacheItem{ - {1, true, 0, 0, []byte{0x00}}, - {2, true, 1, 1, []byte{0x01}}, - {2, true, 0, 2, []byte{0x11}}, - {3, false, 0, 0, []byte{0x02}}, - {3, false, 1, 0, []byte{0x12}}, - {3, false, 2, 0, []byte{0x12}}, + Input: []dtlsflight.HandshakeCacheItem{ + {Typ: 1, IsClient: true, Epoch: 0, MessageSequence: 0, Data: []byte{0x00}}, + {Typ: 2, IsClient: true, Epoch: 1, MessageSequence: 1, Data: []byte{0x01}}, + {Typ: 2, IsClient: true, Epoch: 0, MessageSequence: 2, Data: []byte{0x11}}, + {Typ: 3, IsClient: false, Epoch: 0, MessageSequence: 0, Data: []byte{0x02}}, + {Typ: 3, IsClient: false, Epoch: 1, MessageSequence: 0, Data: []byte{0x12}}, + {Typ: 3, IsClient: false, Epoch: 2, MessageSequence: 0, Data: []byte{0x12}}, }, - Rule: []handshakeCachePullRule{ - {1, 0, true, false}, - {2, 1, true, false}, - {3, 0, false, false}, + Rule: []dtlsflight.HandshakeCachePullRule{ + {Typ: 1, Epoch: 0, IsClient: true, Optional: false}, + {Typ: 2, Epoch: 1, IsClient: true, Optional: false}, + {Typ: 3, Epoch: 0, IsClient: false, Optional: false}, }, Expected: []byte{0x00, 0x01, 0x02}, }, } { - h := newHandshakeCache() + h := dtlsflight.NewCache() for _, i := range test.Input { - h.push(i.data, i.epoch, i.messageSequence, i.typ, i.isClient) + h.Push(i.Data, i.Epoch, i.MessageSequence, i.Typ, i.IsClient) } - verifyData := h.pullAndMerge(test.Rule...) + verifyData := h.PullAndMerge(test.Rule...) assert.Equal(t, test.Expected, verifyData) } } @@ -140,13 +141,13 @@ func TestHandshakeCacheFullPullMapItemsReturnsAcceptedRawItems(t *testing.T) { CompressionMethod: defaultCompressionMethods()[0], }) - cache := newHandshakeCache() - cache.push(rawServerHello, 0, 1, handshake.TypeServerHello, false) - cache.push(rawClientHello, 0, 0, handshake.TypeClientHello, true) + cache := dtlsflight.NewCache() + cache.Push(rawServerHello, 0, 1, handshake.TypeServerHello, false) + cache.Push(rawClientHello, 0, 0, handshake.TypeClientHello, true) - seq, msgs, items, ok := cache.fullPullMapItems(0, nil, - handshakeCachePullRule{handshake.TypeClientHello, 0, true, false}, - handshakeCachePullRule{handshake.TypeServerHello, 0, false, false}, + seq, msgs, items, ok := cache.FullPullMapItems(0, nil, + dtlsflight.HandshakeCachePullRule{Typ: handshake.TypeClientHello, Epoch: 0, IsClient: true, Optional: false}, //nolint:lll + dtlsflight.HandshakeCachePullRule{Typ: handshake.TypeServerHello, Epoch: 0, IsClient: false, Optional: false}, //nolint:lll ) require.True(t, ok) @@ -154,8 +155,8 @@ func TestHandshakeCacheFullPullMapItemsReturnsAcceptedRawItems(t *testing.T) { require.IsType(t, &handshake.MessageClientHello{}, msgs[handshake.TypeClientHello]) require.IsType(t, &handshake.MessageServerHello{}, msgs[handshake.TypeServerHello]) require.Len(t, items, 2) - assert.Equal(t, rawClientHello, items[0].data) - assert.Equal(t, rawServerHello, items[1].data) + assert.Equal(t, rawClientHello, items[0].Data) + assert.Equal(t, rawServerHello, items[1].Data) } func marshalHandshakeCacheTestMessage(t *testing.T, seq uint16, message handshake.Message) []byte { @@ -173,19 +174,19 @@ func marshalHandshakeCacheTestMessage(t *testing.T, seq uint16, message handshak func TestHandshakeCacheSessionHash(t *testing.T) { for _, test := range []struct { Name string - Rule []handshakeCachePullRule - Input []handshakeCacheItem + Rule []dtlsflight.HandshakeCachePullRule + Input []dtlsflight.HandshakeCacheItem Expected []byte }{ { Name: "Standard Handshake", - Input: []handshakeCacheItem{ - {handshake.TypeClientHello, true, 0, 0, []byte{0x00}}, - {handshake.TypeServerHello, false, 0, 1, []byte{0x01}}, - {handshake.TypeCertificate, false, 0, 2, []byte{0x02}}, - {handshake.TypeServerKeyExchange, false, 0, 3, []byte{0x03}}, - {handshake.TypeServerHelloDone, false, 0, 4, []byte{0x04}}, - {handshake.TypeClientKeyExchange, true, 0, 5, []byte{0x05}}, + Input: []dtlsflight.HandshakeCacheItem{ + {Typ: handshake.TypeClientHello, IsClient: true, Epoch: 0, MessageSequence: 0, Data: []byte{0x00}}, + {Typ: handshake.TypeServerHello, IsClient: false, Epoch: 0, MessageSequence: 1, Data: []byte{0x01}}, + {Typ: handshake.TypeCertificate, IsClient: false, Epoch: 0, MessageSequence: 2, Data: []byte{0x02}}, + {Typ: handshake.TypeServerKeyExchange, IsClient: false, Epoch: 0, MessageSequence: 3, Data: []byte{0x03}}, + {Typ: handshake.TypeServerHelloDone, IsClient: false, Epoch: 0, MessageSequence: 4, Data: []byte{0x04}}, + {Typ: handshake.TypeClientKeyExchange, IsClient: true, Epoch: 0, MessageSequence: 5, Data: []byte{0x05}}, }, Expected: []byte{ 0x17, 0xe8, 0x8d, 0xb1, 0x87, 0xaf, 0xd6, 0x2c, 0x16, 0xe5, 0xde, 0xbf, 0x3e, 0x65, 0x27, 0xcd, @@ -194,14 +195,14 @@ func TestHandshakeCacheSessionHash(t *testing.T) { }, { Name: "Handshake With Client Cert Request", - Input: []handshakeCacheItem{ - {handshake.TypeClientHello, true, 0, 0, []byte{0x00}}, - {handshake.TypeServerHello, false, 0, 1, []byte{0x01}}, - {handshake.TypeCertificate, false, 0, 2, []byte{0x02}}, - {handshake.TypeServerKeyExchange, false, 0, 3, []byte{0x03}}, - {handshake.TypeCertificateRequest, false, 0, 4, []byte{0x04}}, - {handshake.TypeServerHelloDone, false, 0, 5, []byte{0x05}}, - {handshake.TypeClientKeyExchange, true, 0, 6, []byte{0x06}}, + Input: []dtlsflight.HandshakeCacheItem{ + {Typ: handshake.TypeClientHello, IsClient: true, Epoch: 0, MessageSequence: 0, Data: []byte{0x00}}, + {Typ: handshake.TypeServerHello, IsClient: false, Epoch: 0, MessageSequence: 1, Data: []byte{0x01}}, + {Typ: handshake.TypeCertificate, IsClient: false, Epoch: 0, MessageSequence: 2, Data: []byte{0x02}}, + {Typ: handshake.TypeServerKeyExchange, IsClient: false, Epoch: 0, MessageSequence: 3, Data: []byte{0x03}}, + {Typ: handshake.TypeCertificateRequest, IsClient: false, Epoch: 0, MessageSequence: 4, Data: []byte{0x04}}, + {Typ: handshake.TypeServerHelloDone, IsClient: false, Epoch: 0, MessageSequence: 5, Data: []byte{0x05}}, + {Typ: handshake.TypeClientKeyExchange, IsClient: true, Epoch: 0, MessageSequence: 6, Data: []byte{0x06}}, }, Expected: []byte{ 0x57, 0x35, 0x5a, 0xc3, 0x30, 0x3c, 0x14, 0x8f, 0x11, 0xae, 0xf7, 0xcb, 0x17, 0x94, 0x56, 0xb9, @@ -210,17 +211,17 @@ func TestHandshakeCacheSessionHash(t *testing.T) { }, { Name: "Handshake Ignores after ClientKeyExchange", - Input: []handshakeCacheItem{ - {handshake.TypeClientHello, true, 0, 0, []byte{0x00}}, - {handshake.TypeServerHello, false, 0, 1, []byte{0x01}}, - {handshake.TypeCertificate, false, 0, 2, []byte{0x02}}, - {handshake.TypeServerKeyExchange, false, 0, 3, []byte{0x03}}, - {handshake.TypeCertificateRequest, false, 0, 4, []byte{0x04}}, - {handshake.TypeServerHelloDone, false, 0, 5, []byte{0x05}}, - {handshake.TypeClientKeyExchange, true, 0, 6, []byte{0x06}}, - {handshake.TypeCertificateVerify, true, 0, 7, []byte{0x07}}, - {handshake.TypeFinished, true, 1, 7, []byte{0x08}}, - {handshake.TypeFinished, false, 1, 7, []byte{0x09}}, + Input: []dtlsflight.HandshakeCacheItem{ + {Typ: handshake.TypeClientHello, IsClient: true, Epoch: 0, MessageSequence: 0, Data: []byte{0x00}}, + {Typ: handshake.TypeServerHello, IsClient: false, Epoch: 0, MessageSequence: 1, Data: []byte{0x01}}, + {Typ: handshake.TypeCertificate, IsClient: false, Epoch: 0, MessageSequence: 2, Data: []byte{0x02}}, + {Typ: handshake.TypeServerKeyExchange, IsClient: false, Epoch: 0, MessageSequence: 3, Data: []byte{0x03}}, + {Typ: handshake.TypeCertificateRequest, IsClient: false, Epoch: 0, MessageSequence: 4, Data: []byte{0x04}}, + {Typ: handshake.TypeServerHelloDone, IsClient: false, Epoch: 0, MessageSequence: 5, Data: []byte{0x05}}, + {Typ: handshake.TypeClientKeyExchange, IsClient: true, Epoch: 0, MessageSequence: 6, Data: []byte{0x06}}, + {Typ: handshake.TypeCertificateVerify, IsClient: true, Epoch: 0, MessageSequence: 7, Data: []byte{0x07}}, + {Typ: handshake.TypeFinished, IsClient: true, Epoch: 1, MessageSequence: 7, Data: []byte{0x08}}, + {Typ: handshake.TypeFinished, IsClient: false, Epoch: 1, MessageSequence: 7, Data: []byte{0x09}}, }, Expected: []byte{ 0x57, 0x35, 0x5a, 0xc3, 0x30, 0x3c, 0x14, 0x8f, 0x11, 0xae, 0xf7, 0xcb, 0x17, 0x94, 0x56, 0xb9, @@ -229,21 +230,21 @@ func TestHandshakeCacheSessionHash(t *testing.T) { }, { Name: "Handshake Ignores wrong epoch", - Input: []handshakeCacheItem{ - {handshake.TypeClientHello, true, 0, 0, []byte{0x00}}, - {handshake.TypeServerHello, false, 0, 1, []byte{0x01}}, - {handshake.TypeCertificate, false, 0, 2, []byte{0x02}}, - {handshake.TypeServerKeyExchange, false, 0, 3, []byte{0x03}}, - {handshake.TypeCertificateRequest, false, 0, 4, []byte{0x04}}, - {handshake.TypeServerHelloDone, false, 0, 5, []byte{0x05}}, - {handshake.TypeClientKeyExchange, true, 0, 6, []byte{0x06}}, - {handshake.TypeCertificateVerify, true, 0, 7, []byte{0x07}}, - {handshake.TypeFinished, true, 0, 7, []byte{0xf0}}, - {handshake.TypeFinished, false, 0, 7, []byte{0xf1}}, - {handshake.TypeFinished, true, 1, 7, []byte{0x08}}, - {handshake.TypeFinished, false, 1, 7, []byte{0x09}}, - {handshake.TypeFinished, true, 0, 7, []byte{0xf0}}, - {handshake.TypeFinished, false, 0, 7, []byte{0xf1}}, + Input: []dtlsflight.HandshakeCacheItem{ + {Typ: handshake.TypeClientHello, IsClient: true, Epoch: 0, MessageSequence: 0, Data: []byte{0x00}}, + {Typ: handshake.TypeServerHello, IsClient: false, Epoch: 0, MessageSequence: 1, Data: []byte{0x01}}, + {Typ: handshake.TypeCertificate, IsClient: false, Epoch: 0, MessageSequence: 2, Data: []byte{0x02}}, + {Typ: handshake.TypeServerKeyExchange, IsClient: false, Epoch: 0, MessageSequence: 3, Data: []byte{0x03}}, + {Typ: handshake.TypeCertificateRequest, IsClient: false, Epoch: 0, MessageSequence: 4, Data: []byte{0x04}}, + {Typ: handshake.TypeServerHelloDone, IsClient: false, Epoch: 0, MessageSequence: 5, Data: []byte{0x05}}, + {Typ: handshake.TypeClientKeyExchange, IsClient: true, Epoch: 0, MessageSequence: 6, Data: []byte{0x06}}, + {Typ: handshake.TypeCertificateVerify, IsClient: true, Epoch: 0, MessageSequence: 7, Data: []byte{0x07}}, + {Typ: handshake.TypeFinished, IsClient: true, Epoch: 0, MessageSequence: 7, Data: []byte{0xf0}}, + {Typ: handshake.TypeFinished, IsClient: false, Epoch: 0, MessageSequence: 7, Data: []byte{0xf1}}, + {Typ: handshake.TypeFinished, IsClient: true, Epoch: 1, MessageSequence: 7, Data: []byte{0x08}}, + {Typ: handshake.TypeFinished, IsClient: false, Epoch: 1, MessageSequence: 7, Data: []byte{0x09}}, + {Typ: handshake.TypeFinished, IsClient: true, Epoch: 0, MessageSequence: 7, Data: []byte{0xf0}}, + {Typ: handshake.TypeFinished, IsClient: false, Epoch: 0, MessageSequence: 7, Data: []byte{0xf1}}, }, Expected: []byte{ 0x57, 0x35, 0x5a, 0xc3, 0x30, 0x3c, 0x14, 0x8f, 0x11, 0xae, 0xf7, 0xcb, 0x17, 0x94, 0x56, 0xb9, @@ -251,13 +252,13 @@ func TestHandshakeCacheSessionHash(t *testing.T) { }, }, } { - h := newHandshakeCache() + h := dtlsflight.NewCache() for _, i := range test.Input { - h.push(i.data, i.epoch, i.messageSequence, i.typ, i.isClient) + h.Push(i.Data, i.Epoch, i.MessageSequence, i.Typ, i.IsClient) } cipherSuite := ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{} - verifyData, err := h.sessionHash(cipherSuite.HashFunc(), 0) + verifyData, err := h.SessionHash(cipherSuite.HashFunc(), 0) assert.NoError(t, err) assert.Equal(t, test.Expected, verifyData, "handshakeCacheSessionHash") } diff --git a/handshake_transcript_13_test.go b/handshake_transcript_13_test.go index 4d4d05668..bbe1aec39 100644 --- a/handshake_transcript_13_test.go +++ b/handshake_transcript_13_test.go @@ -299,7 +299,7 @@ func TestDeriveHandshakeTrafficSecrets13NoHRRAndHRR(t *testing.T) { } func TestDeriveAndStoreHandshakeTrafficSecrets13FromTranscript(t *testing.T) { - cipherSuite := cipherSuiteForID(TLS_AES_128_GCM_SHA256, nil) + cipherSuite := cipherSuiteForID(TLS_AES_128_GCM_SHA256) state := &dtlsstate.State{ CipherSuite: cipherSuite, PreMasterSecret: bytes.Repeat([]byte{0x11}, sha256.Size), @@ -382,7 +382,7 @@ func TestFinishedVerifyData13(t *testing.T) { } func TestDTLS13TranscriptAuthenticatedHandshakeInputs(t *testing.T) { - cipherSuite := cipherSuiteForID(TLS_AES_128_GCM_SHA256, nil) + cipherSuite := cipherSuiteForID(TLS_AES_128_GCM_SHA256) state := &dtlsstate.State{ CipherSuite: cipherSuite, PreMasterSecret: bytes.Repeat([]byte{0x77}, sha256.Size), diff --git a/handshaker.go b/handshaker.go index bd1816994..66ac9af23 100644 --- a/handshaker.go +++ b/handshaker.go @@ -5,21 +5,15 @@ package dtls import ( "context" - "crypto/tls" - "crypto/x509" - "fmt" - "io" - "sync" "time" + dtlsconfig "github.com/pion/dtls/v3/internal/config" dtlserrors "github.com/pion/dtls/v3/internal/errors" + dtlsflight "github.com/pion/dtls/v3/internal/flight" + dtlsflight12 "github.com/pion/dtls/v3/internal/flight/flight12" dtlsstate "github.com/pion/dtls/v3/internal/state" - "github.com/pion/dtls/v3/pkg/crypto/elliptic" - "github.com/pion/dtls/v3/pkg/crypto/signaturehash" - "github.com/pion/dtls/v3/pkg/protocol" "github.com/pion/dtls/v3/pkg/protocol/alert" "github.com/pion/dtls/v3/pkg/protocol/handshake" - "github.com/pion/logging" ) // [RFC6347 Section-4.2.4] @@ -85,86 +79,45 @@ func (s handshakeState) String() string { } type handshakeFSM12 struct { - currentFlight flightVal - flights []*packet + currentFlight dtlsflight.Flight12 + flights []*dtlsflight.Packet retransmit bool retransmitInterval time.Duration state *dtlsstate.State - cache *handshakeCache + cache *dtlsflight.Cache cfg *handshakeConfig closed chan struct{} } -type handshakeConfig struct { - localPSKCallback PSKCallback - localPSKIdentityHint []byte - localCipherSuites []CipherSuite // Available CipherSuites - localSignatureSchemes []signaturehash.Algorithm // Available signature schemes - localCertSignatureSchemes []signaturehash.Algorithm // Available signature schemes for certificates - extendedMasterSecret ExtendedMasterSecretType // Policy for the Extended Master Support extension - localSRTPProtectionProfiles []SRTPProtectionProfile // Available SRTPProtectionProfiles, if empty no SRTP support - localSRTPMasterKeyIdentifier []byte - serverName string - supportedProtocols []string - clientAuth ClientAuthType // If we are a client should we request a client certificate - localCertificates []tls.Certificate - nameToCertificate map[string]*tls.Certificate - insecureSkipVerify bool - verifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error - verifyConnection func(*State) error - sessionStore SessionStore - rootCAs *x509.CertPool - clientCAs *x509.CertPool - initialRetransmitInterval time.Duration - disableRetransmitBackoff bool - customCipherSuites func() []CipherSuite - ellipticCurves []elliptic.Curve - insecureSkipHelloVerify bool - connectionIDGenerator func() []byte - helloRandomBytesGenerator func() [handshake.RandomBytesLength]byte - - onFlightState func(flightVal, handshakeState) - log logging.LeveledLogger - keyLogWriter io.Writer - - localGetCertificate func(*ClientHelloInfo) (*tls.Certificate, error) - localGetClientCertificate func(*CertificateRequestInfo) (*tls.Certificate, error) - - initialEpoch uint16 - - mu sync.Mutex - - clientHelloMessageHook func(handshake.MessageClientHello) handshake.Message - serverHelloMessageHook func(handshake.MessageServerHello) handshake.Message - certificateRequestMessageHook func(handshake.MessageCertificateRequest) handshake.Message - - resumeState *dtlsstate.State - - minVersion protocol.Version - maxVersion protocol.Version - - onFlightState13 func(flightVal13, handshakeState) -} +type handshakeConfig = dtlsconfig.HandshakeConfig type flightConn interface { notify(ctx context.Context, level alert.Level, desc alert.Description) error - writePackets(context.Context, []*packet) error + writePackets(context.Context, []*dtlsflight.Packet) error recvHandshake() <-chan recvHandshakeState setLocalEpoch(epoch uint16) handleQueuedPackets(context.Context) error sessionKey() []byte } -func (c *handshakeConfig) writeKeyLog(label string, clientRandom, secret []byte) { - if c.keyLogWriter == nil { - return - } - c.mu.Lock() - defer c.mu.Unlock() - _, err := fmt.Fprintf(c.keyLogWriter, "%s %x %x\n", label, clientRandom, secret) - if err != nil { - c.log.Debugf("failed to write key log file: %s", err) +type flightConnAdapter struct { + flightConn +} + +func (c flightConnAdapter) HandleQueuedPackets(ctx context.Context) error { + return c.handleQueuedPackets(ctx) +} + +func (c flightConnAdapter) SessionKey() []byte { + return c.sessionKey() +} + +func adaptFlightConn(conn flightConn) dtlsflight.Conn { + if conn == nil { + return nil } + + return flightConnAdapter{conn} } func srvCliStr(isClient bool) string { @@ -176,15 +129,15 @@ func srvCliStr(isClient bool) string { } func newHandshakeFSM12( - s *dtlsstate.State, cache *handshakeCache, cfg *handshakeConfig, - initialFlight flightVal, + s *dtlsstate.State, cache *dtlsflight.Cache, cfg *handshakeConfig, + initialFlight dtlsflight.Flight12, ) *handshakeFSM12 { return &handshakeFSM12{ currentFlight: initialFlight, state: s, cache: cache, cfg: cfg, - retransmitInterval: cfg.initialRetransmitInterval, + retransmitInterval: cfg.InitialRetransmitInterval, closed: make(chan struct{}), } } @@ -205,9 +158,9 @@ func (s *handshakeFSM12) Run(ctx context.Context, conn flightConn, initialState close(s.closed) }() for { - s.cfg.log.Tracef("[handshake:%s] %s: %s", srvCliStr(s.state.IsClient), s.currentFlight.String(), state.String()) - if s.cfg.onFlightState != nil { - s.cfg.onFlightState(s.currentFlight, state) + s.cfg.Log.Tracef("[handshake:%s] %s: %s", srvCliStr(s.state.IsClient), s.currentFlight.String(), state.String()) + if s.cfg.OnFlightState != nil { + s.cfg.OnFlightState(uint8(s.currentFlight), uint8(state)) } var err error switch state { @@ -239,14 +192,14 @@ func (s *handshakeFSM12) prepare(ctx context.Context, conn flightConn) (handshak var ( dtlsAlert *alert.Alert err error - pkts []*packet + pkts []*dtlsflight.Packet ) - gen, retransmit, errFlight := s.currentFlight.getFlightGenerator() - if errFlight != nil { - err = errFlight + gen, retransmit, ok := dtlsflight12.GetGenerator(s.currentFlight) + if !ok { + err = dtlserrors.ErrInvalidFlight dtlsAlert = &alert.Alert{Level: alert.Fatal, Description: alert.InternalError} } else { - pkts, dtlsAlert, err = gen(conn, s.state, s.cache, s.cfg) + pkts, dtlsAlert, err = gen(adaptFlightConn(conn), s.state, s.cache, s.cfg) s.retransmit = retransmit } if dtlsAlert != nil { @@ -261,20 +214,20 @@ func (s *handshakeFSM12) prepare(ctx context.Context, conn flightConn) (handshak } s.flights = pkts - epoch := s.cfg.initialEpoch + epoch := s.cfg.InitialEpoch nextEpoch := epoch for _, p := range s.flights { - p.record.Header.Epoch += epoch - if p.record.Header.Epoch > nextEpoch { - nextEpoch = p.record.Header.Epoch + p.Record.Header.Epoch += epoch + if p.Record.Header.Epoch > nextEpoch { + nextEpoch = p.Record.Header.Epoch } - if h, ok := p.record.Content.(*handshake.Handshake); ok { + if h, ok := p.Record.Content.(*handshake.Handshake); ok { h.Header.MessageSequence = uint16(s.state.HandshakeSendSequence) //nolint:gosec // G115 s.state.HandshakeSendSequence++ } } if epoch != nextEpoch { - s.cfg.log.Tracef("[handshake:%s] -> changeCipherSpec (epoch: %d)", srvCliStr(s.state.IsClient), nextEpoch) + s.cfg.Log.Tracef("[handshake:%s] -> changeCipherSpec (epoch: %d)", srvCliStr(s.state.IsClient), nextEpoch) conn.setLocalEpoch(nextEpoch) } @@ -287,7 +240,7 @@ func (s *handshakeFSM12) send(ctx context.Context, c flightConn) (handshakeState return handshakeErrored, err } - if s.currentFlight.isLastSendFlight() { + if s.currentFlight.IsLastSendFlight() { return handshakeFinished, nil } @@ -295,15 +248,6 @@ func (s *handshakeFSM12) send(ctx context.Context, c flightConn) (handshakeState } func (s *handshakeFSM12) wait(ctx context.Context, conn flightConn) (handshakeState, error) { //nolint:gocognit,cyclop - parse, errFlight := s.currentFlight.getFlightParser() - if errFlight != nil { - if alertErr := conn.notify(ctx, alert.Fatal, alert.InternalError); alertErr != nil { - return handshakeErrored, alertErr - } - - return handshakeErrored, errFlight - } - retransmitTimer := time.NewTimer(s.retransmitInterval) for { select { @@ -311,13 +255,27 @@ func (s *handshakeFSM12) wait(ctx context.Context, conn flightConn) (handshakeSt if !state.isRetransmit { // only reset retransmit interval on non-retransmit state // https://github.com/pion/dtls/issues/758 - s.retransmitInterval = s.cfg.initialRetransmitInterval + s.retransmitInterval = s.cfg.InitialRetransmitInterval } - nextFlight, alert, err := parse(ctx, conn, s.state, s.cache, s.cfg) + nextFlight, dtlsAlert, err, ok := dtlsflight12.Parse( + ctx, + s.currentFlight, + adaptFlightConn(conn), + s.state, + s.cache, + s.cfg, + ) + if !ok { + if alertErr := conn.notify(ctx, alert.Fatal, alert.InternalError); alertErr != nil { + return handshakeErrored, alertErr + } + + return handshakeErrored, dtlserrors.ErrInvalidFlight + } close(state.done) - if alert != nil { - if alertErr := conn.notify(ctx, alert.Level, alert.Description); alertErr != nil { + if dtlsAlert != nil { + if alertErr := conn.notify(ctx, dtlsAlert.Level, dtlsAlert.Description); alertErr != nil { if err != nil { err = alertErr } @@ -329,13 +287,13 @@ func (s *handshakeFSM12) wait(ctx context.Context, conn flightConn) (handshakeSt if nextFlight == 0 { break } - s.cfg.log.Tracef( + s.cfg.Log.Tracef( "[handshake:%s] %s -> %s", srvCliStr(s.state.IsClient), s.currentFlight.String(), nextFlight.String(), ) - if nextFlight.isLastRecvFlight() && s.currentFlight == nextFlight { + if nextFlight.IsLastRecvFlight() && s.currentFlight == nextFlight { return handshakeFinished, nil } s.currentFlight = nextFlight @@ -350,7 +308,7 @@ func (s *handshakeFSM12) wait(ctx context.Context, conn flightConn) (handshakeSt // RFC 4347 4.2.4.1: // Implementations SHOULD use an initial timer value of 1 second (the minimum defined in RFC 2988 [RFC2988]) // and double the value at each retransmission, up to no less than the RFC 2988 maximum of 60 seconds. - if !s.cfg.disableRetransmitBackoff { + if !s.cfg.DisableRetransmitBackoff { s.retransmitInterval *= 2 } if s.retransmitInterval > time.Second*60 { @@ -359,7 +317,7 @@ func (s *handshakeFSM12) wait(ctx context.Context, conn flightConn) (handshakeSt return handshakeSending, nil case <-ctx.Done(): - s.retransmitInterval = s.cfg.initialRetransmitInterval + s.retransmitInterval = s.cfg.InitialRetransmitInterval return handshakeErrored, ctx.Err() } diff --git a/handshaker_13.go b/handshaker_13.go index 7f68c9e7d..bd50c3e9f 100644 --- a/handshaker_13.go +++ b/handshaker_13.go @@ -10,6 +10,8 @@ import ( "time" dtlserrors "github.com/pion/dtls/v3/internal/errors" + dtlsflight "github.com/pion/dtls/v3/internal/flight" + dtlsflight13 "github.com/pion/dtls/v3/internal/flight/flight13" dtlsstate "github.com/pion/dtls/v3/internal/state" "github.com/pion/dtls/v3/pkg/protocol/alert" "github.com/pion/dtls/v3/pkg/protocol/handshake" @@ -65,12 +67,12 @@ import ( // Retransmit ACK type handshakeFSM13 struct { - currentFlight flightVal13 - flights []*packet //nolint:unused - retransmit bool //nolint:unused + currentFlight dtlsflight.Flight13 + flights []*dtlsflight.Packet //nolint:unused + retransmit bool //nolint:unused retransmitInterval time.Duration state *dtlsstate.State - cache *handshakeCache + cache *dtlsflight.Cache cfg *handshakeConfig transcript *handshakeTranscript13 closed chan struct{} @@ -78,10 +80,10 @@ type handshakeFSM13 struct { func newHandshakeFSM13( state *dtlsstate.State, - cache *handshakeCache, + cache *dtlsflight.Cache, cfg *handshakeConfig, - initialFlight flightVal13, - initialFlights []*packet, + initialFlight dtlsflight.Flight13, + initialFlights []*dtlsflight.Packet, initialTranscript *handshakeTranscript13, ) (*handshakeFSM13, error) { if initialTranscript == nil { @@ -96,7 +98,7 @@ func newHandshakeFSM13( cache: cache, cfg: cfg, transcript: initialTranscript, - retransmitInterval: cfg.initialRetransmitInterval, + retransmitInterval: cfg.InitialRetransmitInterval, closed: make(chan struct{}), } if err := fsm.seedTranscriptFromInitialFlights(); err != nil { @@ -106,15 +108,6 @@ func newHandshakeFSM13( return fsm, nil } -func (s *handshakeFSM13) flightContext() *handshakeContext13 { - return &handshakeContext13{ - state: s.state, - cache: s.cache, - cfg: s.cfg, - transcript: s.transcript, - } -} - // seedTranscriptFromInitialFlights handles the dual-stack ClientHello generated // before the DTLS 1.3 FSM exists. func (s *handshakeFSM13) seedTranscriptFromInitialFlights() error { @@ -133,7 +126,7 @@ func (s *handshakeFSM13) seedTranscriptFromInitialFlights() error { return nil } -func appendClientHelloInitialFlights13(transcript *handshakeTranscript13, flights []*packet) (bool, error) { +func appendClientHelloInitialFlights13(transcript *handshakeTranscript13, flights []*dtlsflight.Packet) (bool, error) { if transcript == nil { return false, dtlserrors.ErrHandshakeTranscriptMissingClientHello } @@ -159,11 +152,11 @@ func appendClientHelloInitialFlights13(transcript *handshakeTranscript13, flight return appended, nil } -func canonicalClientHelloInitialFlight13(p *packet) (uint16, []byte, bool, error) { - if p == nil || p.record == nil { +func canonicalClientHelloInitialFlight13(p *dtlsflight.Packet) (uint16, []byte, bool, error) { + if p == nil || p.Record == nil { return 0, nil, false, nil } - hand, ok := p.record.Content.(*handshake.Handshake) + hand, ok := p.Record.Content.(*handshake.Handshake) if !ok { return 0, nil, false, nil } @@ -190,11 +183,11 @@ func (s *handshakeFSM13) Run(ctx context.Context, conn flightConn, initialState close(s.closed) }() for { - s.cfg.log.Tracef("[handshake13:%s] %s: %s", srvCliStr(s.state.IsClient), s.currentFlight.String(), state.String()) + s.cfg.Log.Tracef("[handshake13:%s] %s: %s", srvCliStr(s.state.IsClient), s.currentFlight.String(), state.String()) // nolint:godox // TODO:: refactor callback, see discussion in https://github.com/pion/dtls/pull/738#discussion_r3131501159 - if s.cfg.onFlightState13 != nil { - s.cfg.onFlightState13(s.currentFlight, state) + if s.cfg.OnFlightState13 != nil { + s.cfg.OnFlightState13(uint8(s.currentFlight), uint8(state)) } var err error switch state { @@ -226,14 +219,14 @@ func (s *handshakeFSM13) prepare(ctx context.Context, conn flightConn) (handshak var ( dtlsAlert *alert.Alert err error - pkts []*packet + pkts []*dtlsflight.Packet ) - gen, retransmit, errFlight := s.currentFlight.getFlightGenerator13() - if errFlight != nil { - err = errFlight + gen, retransmit, ok := dtlsflight13.GetGenerator(s.currentFlight) + if !ok { + err = dtlserrors.ErrFlightUnimplemented13 dtlsAlert = &alert.Alert{Level: alert.Fatal, Description: alert.InternalError} } else { - pkts, dtlsAlert, err = gen(conn, s.flightContext()) + pkts, dtlsAlert, err = gen(adaptFlightConn(conn), s.state, s.cache, s.cfg) s.retransmit = retransmit } if dtlsAlert != nil { @@ -256,14 +249,14 @@ func (s *handshakeFSM13) prepare(ctx context.Context, conn flightConn) (handshak } func (s *handshakeFSM13) commitPreparedFlights(conn flightConn) error { - epoch := s.cfg.initialEpoch + epoch := s.cfg.InitialEpoch nextEpoch := epoch for _, p := range s.flights { - p.record.Header.Epoch += epoch - if p.record.Header.Epoch > nextEpoch { - nextEpoch = p.record.Header.Epoch + p.Record.Header.Epoch += epoch + if p.Record.Header.Epoch > nextEpoch { + nextEpoch = p.Record.Header.Epoch } - if h, ok := p.record.Content.(*handshake.Handshake); ok { + if h, ok := p.Record.Content.(*handshake.Handshake); ok { h.Header.MessageSequence = uint16(s.state.HandshakeSendSequence) //nolint:gosec // G115 s.state.HandshakeSendSequence++ } @@ -277,7 +270,7 @@ func (s *handshakeFSM13) commitPreparedFlights(conn flightConn) error { return err } if epoch != nextEpoch { - s.cfg.log.Tracef("[handshake13:%s] -> changeCipherSpec (epoch: %d)", srvCliStr(s.state.IsClient), nextEpoch) + s.cfg.Log.Tracef("[handshake13:%s] -> changeCipherSpec (epoch: %d)", srvCliStr(s.state.IsClient), nextEpoch) conn.setLocalEpoch(nextEpoch) } @@ -321,7 +314,7 @@ func appendOutboundHandshakeFlight13( transcript *handshakeTranscript13, isClient bool, cipherSuite CipherSuite, - pkts []*packet, + pkts []*dtlsflight.Packet, ) error { if transcript == nil { return nil @@ -345,12 +338,12 @@ func appendOutboundHandshakeFlight13( return nil } -func canonicalOutboundHandshake13(p *packet) (*handshake.Handshake, []byte, bool, error) { - if p == nil || p.record == nil { +func canonicalOutboundHandshake13(p *dtlsflight.Packet) (*handshake.Handshake, []byte, bool, error) { + if p == nil || p.Record == nil { return nil, nil, false, nil } - hs, ok := p.record.Content.(*handshake.Handshake) + hs, ok := p.Record.Content.(*handshake.Handshake) if !ok || hs.Message == nil { return nil, nil, false, nil } @@ -380,15 +373,18 @@ func appendOutboundHandshake13( func appendInboundHandshakeCacheItems13( transcript *handshakeTranscript13, cipherSuite CipherSuite, - items []*handshakeCacheItem, + items []*dtlsflight.HandshakeCacheItem, ) error { if transcript == nil { return nil } - keyExchangeAlgorithm := keyExchangeAlgorithmForCipherSuite(cipherSuite) + var keyExchangeAlgorithm CipherSuiteKeyExchangeAlgorithm + if cipherSuite != nil { + keyExchangeAlgorithm = cipherSuite.KeyExchangeAlgorithm() + } for _, item := range items { - canonical, err := canonicalHandshake13(item.data) + canonical, err := canonicalHandshake13(item.Data) if err != nil { return err } @@ -396,13 +392,13 @@ func appendInboundHandshakeCacheItems13( h := &handshake.Handshake{ KeyExchangeAlgorithm: keyExchangeAlgorithm, } - if err := h.Unmarshal(item.data); err != nil { + if err := h.Unmarshal(item.Data); err != nil { return err } if err := appendHandshake13( transcript, - transcriptSenderForSide13(item.isClient), + transcriptSenderForSide13(item.IsClient), cipherSuite, h.Header.MessageSequence, h.Message, @@ -427,7 +423,7 @@ func appendHandshake13( sender: sender, seq: seq, } - if sh, ok := message.(*handshake.MessageServerHello); ok && isHelloRetryRequest(sh) { + if sh, ok := message.(*handshake.MessageServerHello); ok && dtlsflight13.IsHelloRetryRequest(sh) { duplicate, err := transcript.hasCanonical13(id, canonical) if err != nil || duplicate { return err diff --git a/handshaker_13_test.go b/handshaker_13_test.go index c018af585..c1f152405 100644 --- a/handshaker_13_test.go +++ b/handshaker_13_test.go @@ -8,7 +8,9 @@ import ( "testing" "time" + dtlsconfig "github.com/pion/dtls/v3/internal/config" dtlserrors "github.com/pion/dtls/v3/internal/errors" + dtlsflight "github.com/pion/dtls/v3/internal/flight" dtlsstate "github.com/pion/dtls/v3/internal/state" "github.com/pion/dtls/v3/pkg/crypto/signaturehash" "github.com/pion/dtls/v3/pkg/protocol" @@ -22,10 +24,10 @@ import ( func TestHandshakeFSM13OwnsTranscriptAndPropagatesContext(t *testing.T) { state := &dtlsstate.State{IsClient: true, LocalVersion: protocol.Version1_3} - cache := newHandshakeCache() + cache := dtlsflight.NewCache() cfg := testHandshakeConfig13(t) - fsm, err := newHandshakeFSM13(state, cache, cfg, flight13_1, nil, nil) + fsm, err := newHandshakeFSM13(state, cache, cfg, dtlsflight.Flight13_1, nil, nil) require.NoError(t, err) require.NotNil(t, fsm.transcript) @@ -38,16 +40,16 @@ func TestHandshakeFSM13OwnsTranscriptAndPropagatesContext(t *testing.T) { func TestHandshakeFSM13DualStackClientHelloSeedsTranscript(t *testing.T) { state := &dtlsstate.State{IsClient: true, LocalVersion: protocol.Version1_3} - cache := newHandshakeCache() + cache := dtlsflight.NewCache() cfg := testHandshakeConfig13(t) - cfg.clientHelloMessageHook = func(ch handshake.MessageClientHello) handshake.Message { + cfg.ClientHelloMessageHook = func(ch handshake.MessageClientHello) handshake.Message { ch.SessionID = []byte{0xaa, 0xbb} return &ch } transcript := newHandshakeTranscript13() - pkts, dtlsAlert, err := flight13_1Generate(nil, &handshakeContext13{ + pkts, dtlsAlert, err := flight13GenerateForTest(t, dtlsflight.Flight13_1, &handshakeContext13{ state: state, cache: cache, cfg: cfg, @@ -58,7 +60,7 @@ func TestHandshakeFSM13DualStackClientHelloSeedsTranscript(t *testing.T) { require.Len(t, pkts, 1) const messageSequence = 7 - content, ok := pkts[0].record.Content.(*handshake.Handshake) + content, ok := pkts[0].Record.Content.(*handshake.Handshake) require.True(t, ok) content.Header.MessageSequence = messageSequence @@ -67,7 +69,7 @@ func TestHandshakeFSM13DualStackClientHelloSeedsTranscript(t *testing.T) { require.NoError(t, err) require.True(t, appended) - fsm, err := newHandshakeFSM13(state, cache, cfg, flight13_1, pkts, transcript) + fsm, err := newHandshakeFSM13(state, cache, cfg, dtlsflight.Flight13_1, pkts, transcript) require.NoError(t, err) require.NotNil(t, fsm.transcript) require.Same(t, transcript, fsm.transcript) @@ -85,11 +87,11 @@ func TestHandshakeFSM13DualStackClientHelloSeedsTranscript(t *testing.T) { func TestHandshakeFSM13TranscriptSurvivesStateChangesAndRetransmitSeed(t *testing.T) { state := &dtlsstate.State{IsClient: true, LocalVersion: protocol.Version1_3} - cache := newHandshakeCache() + cache := dtlsflight.NewCache() cfg := testHandshakeConfig13(t) transcript := newHandshakeTranscript13() - pkts, dtlsAlert, err := flight13_1Generate(nil, &handshakeContext13{ + pkts, dtlsAlert, err := flight13GenerateForTest(t, dtlsflight.Flight13_1, &handshakeContext13{ state: state, cache: cache, cfg: cfg, @@ -98,14 +100,14 @@ func TestHandshakeFSM13TranscriptSurvivesStateChangesAndRetransmitSeed(t *testin require.NoError(t, err) require.Nil(t, dtlsAlert) - fsm, err := newHandshakeFSM13(state, cache, cfg, flight13_1, pkts, transcript) + fsm, err := newHandshakeFSM13(state, cache, cfg, dtlsflight.Flight13_1, pkts, transcript) require.NoError(t, err) transcript = fsm.transcript before := append([]byte(nil), transcript.transcript...) require.Len(t, transcript.pending, 1) - fsm.currentFlight = flight13_2 + fsm.currentFlight = dtlsflight.Flight13_2 fsm.retransmit = true fsm.retransmitInterval *= 2 @@ -121,10 +123,12 @@ func TestHandshakeFSM13TranscriptSurvivesStateChangesAndRetransmitSeed(t *testin func TestHandshakeFSM13DualStackClientHelloRequired(t *testing.T) { state := &dtlsstate.State{IsClient: true, LocalVersion: protocol.Version1_3} - cache := newHandshakeCache() + cache := dtlsflight.NewCache() cfg := testHandshakeConfig13(t) - fsm, err := newHandshakeFSM13(state, cache, cfg, flight13_1, []*packet{}, newHandshakeTranscript13()) + fsm, err := newHandshakeFSM13( + state, cache, cfg, dtlsflight.Flight13_1, []*dtlsflight.Packet{}, newHandshakeTranscript13(), + ) require.Nil(t, fsm) require.ErrorIs(t, err, dtlserrors.ErrHandshakeTranscriptMissingClientHello) } @@ -133,11 +137,11 @@ func TestHandshakeFSM13PrepareHelloRetryRequestRequiresSeededTranscript(t *testi cfg := testHandshakeConfig13(t) state := &dtlsstate.State{ LocalVersion: protocol.Version1_3, - CipherSuite: cfg.localCipherSuites[0], + CipherSuite: cfg.LocalCipherSuites[0], } - cache := newHandshakeCache() + cache := dtlsflight.NewCache() - fsm, err := newHandshakeFSM13(state, cache, cfg, flight13_2, nil, nil) + fsm, err := newHandshakeFSM13(state, cache, cfg, dtlsflight.Flight13_2, nil, nil) require.NoError(t, err) nextState, err := fsm.prepare(context.Background(), nil) @@ -152,9 +156,9 @@ func TestHandshakeFSM13PrepareHelloRetryRequestRequiresSeededTranscript(t *testi func TestHandshakeFSM13PrepareCommitsOutboundClientHello(t *testing.T) { cfg := testHandshakeConfig13(t) state := &dtlsstate.State{IsClient: true, LocalVersion: protocol.Version1_3} - cache := newHandshakeCache() + cache := dtlsflight.NewCache() - fsm, err := newHandshakeFSM13(state, cache, cfg, flight13_1, nil, nil) + fsm, err := newHandshakeFSM13(state, cache, cfg, dtlsflight.Flight13_1, nil, nil) require.NoError(t, err) nextState, err := fsm.prepare(context.Background(), nil) @@ -174,15 +178,15 @@ func TestHandshakeFSM13PrepareCommitsOutboundHelloRetryRequestWithSeededTranscri cfg := testHandshakeConfig13(t) state := &dtlsstate.State{ LocalVersion: protocol.Version1_3, - CipherSuite: cfg.localCipherSuites[0], + CipherSuite: cfg.LocalCipherSuites[0], } - cache := newHandshakeCache() + cache := dtlsflight.NewCache() transcript := newHandshakeTranscript13() clientHello := transcriptTestClientHelloPacket13([]byte{0x01}, 0) clientHelloCanonical := canonicalPacketHandshake13(t, clientHello) - require.NoError(t, appendOutboundHandshakeFlight13(transcript, true, nil, []*packet{clientHello})) + require.NoError(t, appendOutboundHandshakeFlight13(transcript, true, nil, []*dtlsflight.Packet{clientHello})) - fsm, err := newHandshakeFSM13(state, cache, cfg, flight13_2, nil, transcript) + fsm, err := newHandshakeFSM13(state, cache, cfg, dtlsflight.Flight13_2, nil, transcript) require.NoError(t, err) nextState, err := fsm.prepare(context.Background(), nil) @@ -201,10 +205,10 @@ func TestHandshakeFSM13PrepareCommitsOutboundHelloRetryRequestWithSeededTranscri assert.Equal(t, 1, state.HandshakeSendSequence) } -func canonicalPacketHandshake13(t *testing.T, p *packet) []byte { +func canonicalPacketHandshake13(t *testing.T, p *dtlsflight.Packet) []byte { t.Helper() - content, ok := p.record.Content.(*handshake.Handshake) + content, ok := p.Record.Content.(*handshake.Handshake) require.True(t, ok) raw, err := content.Marshal() require.NoError(t, err) @@ -230,16 +234,16 @@ func testHandshakeConfig13(t *testing.T) *handshakeConfig { loggerFactory := logging.NewDefaultLoggerFactory() return &handshakeConfig{ - localCipherSuites: cipherSuites, - ellipticCurves: defaultCurves, - initialRetransmitInterval: time.Second, - extendedMasterSecret: RequestExtendedMasterSecret, - log: loggerFactory.NewLogger("dtls"), - minVersion: protocol.Version1_3, - maxVersion: protocol.Version1_3, - localSignatureSchemes: signaturehash.Algorithms13(), - localCertSignatureSchemes: nil, - localSRTPProtectionProfiles: nil, + LocalCipherSuites: cipherSuites, + EllipticCurves: defaultCurves, + InitialRetransmitInterval: time.Second, + ExtendedMasterSecret: dtlsconfig.ExtendedMasterSecretType(RequestExtendedMasterSecret), + Log: loggerFactory.NewLogger("dtls"), + MinVersion: protocol.Version1_3, + MaxVersion: protocol.Version1_3, + LocalSignatureSchemes: signaturehash.Algorithms13(), + LocalCertSignatureSchemes: nil, + LocalSRTPProtectionProfiles: nil, } } @@ -248,7 +252,7 @@ func TestAppendOutboundHandshakeFlight13ClientHello(t *testing.T) { pkt := transcriptTestClientHelloPacket13([]byte{0x01}, 3) expected := canonicalPacketHandshake13(t, pkt) - err := appendOutboundHandshakeFlight13(transcript, true, nil, []*packet{pkt}) + err := appendOutboundHandshakeFlight13(transcript, true, nil, []*dtlsflight.Packet{pkt}) require.NoError(t, err) require.Len(t, transcript.order, 1) require.Len(t, transcript.pending, 1) @@ -263,10 +267,10 @@ func TestAppendOutboundHandshakeFlight13DuplicateNoop(t *testing.T) { transcript := newHandshakeTranscript13() pkt := transcriptTestClientHelloPacket13([]byte{0x01}, 0) - require.NoError(t, appendOutboundHandshakeFlight13(transcript, true, nil, []*packet{pkt})) + require.NoError(t, appendOutboundHandshakeFlight13(transcript, true, nil, []*dtlsflight.Packet{pkt})) before := append([]byte(nil), transcript.transcript...) - require.NoError(t, appendOutboundHandshakeFlight13(transcript, true, nil, []*packet{pkt})) + require.NoError(t, appendOutboundHandshakeFlight13(transcript, true, nil, []*dtlsflight.Packet{pkt})) assert.Equal(t, before, transcript.transcript) assert.Len(t, transcript.order, 1) } @@ -276,15 +280,15 @@ func TestAppendOutboundHandshakeFlight13ChangedSameSequenceFails(t *testing.T) { pkt := transcriptTestClientHelloPacket13([]byte{0x01}, 0) changedPkt := transcriptTestClientHelloPacket13([]byte{0x02}, 0) - require.NoError(t, appendOutboundHandshakeFlight13(transcript, true, nil, []*packet{pkt})) - err := appendOutboundHandshakeFlight13(transcript, true, nil, []*packet{changedPkt}) + require.NoError(t, appendOutboundHandshakeFlight13(transcript, true, nil, []*dtlsflight.Packet{pkt})) + err := appendOutboundHandshakeFlight13(transcript, true, nil, []*dtlsflight.Packet{changedPkt}) assert.ErrorIs(t, err, dtlserrors.ErrHandshakeTranscriptMessageChanged) } func TestAppendOutboundHandshakeFlight13HelloRetryRequest(t *testing.T) { cfg := testHandshakeConfig13(t) - cipherSuite := cfg.localCipherSuites[0] + cipherSuite := cfg.LocalCipherSuites[0] transcript := newHandshakeTranscript13() clientHello := transcriptTestClientHelloPacket13([]byte{0x01}, 0) helloRetryRequest := transcriptTestHelloRetryRequestPacket13(t, cipherSuite, 0) @@ -292,8 +296,10 @@ func TestAppendOutboundHandshakeFlight13HelloRetryRequest(t *testing.T) { clientHelloCanonical := canonicalPacketHandshake13(t, clientHello) helloRetryRequestCanonical := canonicalPacketHandshake13(t, helloRetryRequest) - require.NoError(t, appendOutboundHandshakeFlight13(transcript, true, cipherSuite, []*packet{clientHello})) - require.NoError(t, appendOutboundHandshakeFlight13(transcript, false, cipherSuite, []*packet{helloRetryRequest})) + require.NoError(t, appendOutboundHandshakeFlight13(transcript, true, cipherSuite, []*dtlsflight.Packet{clientHello})) + require.NoError(t, appendOutboundHandshakeFlight13( + transcript, false, cipherSuite, []*dtlsflight.Packet{helloRetryRequest}, + )) clientHelloHash := hashTranscript13(clientHelloCanonical) messageHash := canonicalTranscriptHandshake13(handshake.TypeMessageHash, clientHelloHash) @@ -308,14 +314,16 @@ func TestAppendOutboundHandshakeFlight13HelloRetryRequest(t *testing.T) { assert.Equal(t, handshake.TypeServerHello, transcript.order[1].typ) before := append([]byte(nil), transcript.transcript...) - require.NoError(t, appendOutboundHandshakeFlight13(transcript, false, cipherSuite, []*packet{helloRetryRequest})) + require.NoError(t, appendOutboundHandshakeFlight13( + transcript, false, cipherSuite, []*dtlsflight.Packet{helloRetryRequest}, + )) assert.Equal(t, before, transcript.transcript) assert.Len(t, transcript.order, 2) } -func transcriptTestClientHelloPacket13(sessionID []byte, seq uint16) *packet { - return &packet{ - record: &recordlayer.RecordLayer{ +func transcriptTestClientHelloPacket13(sessionID []byte, seq uint16) *dtlsflight.Packet { + return &dtlsflight.Packet{ + Record: &recordlayer.RecordLayer{ Header: recordlayer.Header{ Version: protocol.Version1_2, }, @@ -332,15 +340,15 @@ func transcriptTestClientHelloPacket13(sessionID []byte, seq uint16) *packet { } } -func transcriptTestHelloRetryRequestPacket13(tb testing.TB, cipherSuite CipherSuite, seq uint16) *packet { +func transcriptTestHelloRetryRequestPacket13(tb testing.TB, cipherSuite CipherSuite, seq uint16) *dtlsflight.Packet { tb.Helper() random := handshake.Random{} random.UnmarshalFixed([32]byte(handshake.HelloRetryRequestRandom())) cipherSuiteID := uint16(cipherSuite.ID()) - return &packet{ - record: &recordlayer.RecordLayer{ + return &dtlsflight.Packet{ + Record: &recordlayer.RecordLayer{ Header: recordlayer.Header{ Version: protocol.Version1_2, }, diff --git a/handshaker_test.go b/handshaker_test.go index 98b7505cf..546a64461 100644 --- a/handshaker_test.go +++ b/handshaker_test.go @@ -12,6 +12,8 @@ import ( "testing" "time" + dtlsconfig "github.com/pion/dtls/v3/internal/config" + dtlsflight "github.com/pion/dtls/v3/internal/flight" dtlsstate "github.com/pion/dtls/v3/internal/state" "github.com/pion/dtls/v3/pkg/crypto/selfsign" "github.com/pion/dtls/v3/pkg/crypto/signaturehash" @@ -30,9 +32,9 @@ const nonZeroRetransmitInterval = 100 * time.Millisecond func TestWriteKeyLog(t *testing.T) { var buf bytes.Buffer cfg := handshakeConfig{ - keyLogWriter: &buf, + KeyLogWriter: &buf, } - cfg.writeKeyLog("LABEL", []byte{0xAA, 0xBB, 0xCC}, []byte{0xDD, 0xEE, 0xFF}) + cfg.WriteKeyLog("LABEL", []byte{0xAA, 0xBB, 0xCC}, []byte{0xDD, 0xEE, 0xFF}) // Secrets follow the format