Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ type Config struct {
// SignatureSchemes contains the signature and hash schemes that the peer requests to verify.
SignatureSchemes []tls.SignatureScheme

// CertificateSignatureSchemes contains the signature and hash schemes that may be used
// in digital signatures for X.509 certificates. If not set, the signature_algorithms_cert
// extension is not sent, and SignatureSchemes is used for both handshake signatures and
// certificate chain validation, as specified in RFC 8446 Section 4.2.3.
CertificateSignatureSchemes []tls.SignatureScheme

// SRTPProtectionProfiles are the supported protection profiles
// Clients will send this via use_srtp and assert that the server properly responds
// Servers will assert that clients send one of these profiles and will respond as needed
Expand Down
13 changes: 13 additions & 0 deletions conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,18 @@ func createConn(
return nil, err
}

// Parse certificate signature schemes only if explicitly configured
var certSignatureSchemes []signaturehash.Algorithm
if len(config.CertificateSignatureSchemes) > 0 {
certSignatureSchemes, err = signaturehash.ParseSignatureSchemes(
config.CertificateSignatureSchemes,
config.InsecureHashes,
)
if err != nil {
return nil, err
}
}

workerInterval := initialTickerInterval
if config.FlightInterval > 0 {
workerInterval = config.FlightInterval
Expand All @@ -174,6 +186,7 @@ func createConn(
localPSKIdentityHint: config.PSKIdentityHint,
localCipherSuites: cipherSuites,
localSignatureSchemes: signatureSchemes,
localCertSignatureSchemes: certSignatureSchemes,
extendedMasterSecret: config.ExtendedMasterSecret,
localSRTPProtectionProfiles: config.SRTPProtectionProfiles,
localSRTPMasterKeyIdentifier: config.SRTPMasterKeyIdentifier,
Expand Down
74 changes: 71 additions & 3 deletions crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/pion/dtls/v3/pkg/crypto/elliptic"
"github.com/pion/dtls/v3/pkg/crypto/hash"
"github.com/pion/dtls/v3/pkg/crypto/signature"
"github.com/pion/dtls/v3/pkg/crypto/signaturehash"
)

type ecdsaSignature struct {
Expand Down Expand Up @@ -328,7 +329,11 @@ func loadCerts(rawCertificates [][]byte) ([]*x509.Certificate, error) {
return certs, nil
}

func verifyClientCert(rawCertificates [][]byte, roots *x509.CertPool) (chains [][]*x509.Certificate, err error) {
func verifyClientCert(
rawCertificates [][]byte,
roots *x509.CertPool,
certSignatureSchemes []signaturehash.Algorithm,
) (chains [][]*x509.Certificate, err error) {
certificate, err := loadCerts(rawCertificates)
if err != nil {
return nil, err
Expand All @@ -344,13 +349,26 @@ func verifyClientCert(rawCertificates [][]byte, roots *x509.CertPool) (chains []
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}

return certificate[0].Verify(opts)
chains, err = certificate[0].Verify(opts)
if err != nil {
return nil, err
}

// Validate certificate signature algorithms if specified
if len(certSignatureSchemes) > 0 && len(chains) > 0 {
if err := validateCertificateSignatureAlgorithms(chains[0], certSignatureSchemes); err != nil {
return nil, err
}
}

return chains, nil
}

func verifyServerCert(
rawCertificates [][]byte,
roots *x509.CertPool,
serverName string,
certSignatureSchemes []signaturehash.Algorithm,
) (chains [][]*x509.Certificate, err error) {
certificate, err := loadCerts(rawCertificates)
if err != nil {
Expand All @@ -367,5 +385,55 @@ func verifyServerCert(
Intermediates: intermediateCAPool,
}

return certificate[0].Verify(opts)
chains, err = certificate[0].Verify(opts)
if err != nil {
return nil, err
}

// Validate certificate signature algorithms if specified
if len(certSignatureSchemes) > 0 && len(chains) > 0 {
if err := validateCertificateSignatureAlgorithms(chains[0], certSignatureSchemes); err != nil {
return nil, err
}
}

return chains, nil
}

// validateCertificateSignatureAlgorithms validates that all certificates in the chain
// use signature algorithms that are in the allowed list. This implements the
// signature_algorithms_cert extension validation per RFC 8446 Section 4.2.3.
func validateCertificateSignatureAlgorithms(
certs []*x509.Certificate,
allowedAlgorithms []signaturehash.Algorithm,
) error {
if len(allowedAlgorithms) == 0 {
// No restrictions specified
return nil
}

// Validate each certificate's signature algorithm (except the root, which we trust)
for i := 0; i < len(certs)-1; i++ {
cert := certs[i]
certAlg, err := signaturehash.FromCertificate(cert)
if err != nil {
return err
}

// Check if this algorithm is in the allowed list
found := false
for _, allowed := range allowedAlgorithms {
if certAlg.Hash == allowed.Hash && certAlg.Signature == allowed.Signature {
found = true

break
}
}

if !found {
return errInvalidCertificateSignatureAlgorithm
}
}

return nil
}
149 changes: 149 additions & 0 deletions crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/pion/dtls/v3/pkg/crypto/elliptic"
"github.com/pion/dtls/v3/pkg/crypto/hash"
"github.com/pion/dtls/v3/pkg/crypto/signature"
"github.com/pion/dtls/v3/pkg/crypto/signaturehash"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -353,3 +354,151 @@ func TestCertificateOIDValidation(t *testing.T) {
assert.ErrorIs(t, err, errInvalidCertificateOID)
})
}

func TestValidateCertificateSignatureAlgorithms(t *testing.T) {
// Helper to create a test certificate with specific signature algorithm
createTestCert := func(sigAlg x509.SignatureAlgorithm, isCA bool) *x509.Certificate {
return &x509.Certificate{
SerialNumber: big.NewInt(1),
SignatureAlgorithm: sigAlg,
IsCA: isCA,
}
}

t.Run("Empty allowed list passes", func(t *testing.T) {
certs := []*x509.Certificate{
createTestCert(x509.SHA256WithRSA, false),
}
err := validateCertificateSignatureAlgorithms(certs, nil)
assert.NoError(t, err)
})

t.Run("Single cert with allowed algorithm passes", func(t *testing.T) {
certs := []*x509.Certificate{
createTestCert(x509.SHA256WithRSA, false),
createTestCert(x509.SHA256WithRSA, true), // Root
}
allowed := []signaturehash.Algorithm{
{Hash: hash.SHA256, Signature: signature.RSA},
}
err := validateCertificateSignatureAlgorithms(certs, allowed)
assert.NoError(t, err)
})

t.Run("Single cert with disallowed algorithm fails", func(t *testing.T) {
certs := []*x509.Certificate{
createTestCert(x509.SHA256WithRSA, false),
createTestCert(x509.SHA256WithRSA, true), // Root
}
allowed := []signaturehash.Algorithm{
{Hash: hash.SHA384, Signature: signature.ECDSA}, // Different algorithm
}
err := validateCertificateSignatureAlgorithms(certs, allowed)
assert.ErrorIs(t, err, errInvalidCertificateSignatureAlgorithm)
})

t.Run("Root certificate is not validated", func(t *testing.T) {
certs := []*x509.Certificate{
createTestCert(x509.SHA256WithRSA, false), // Leaf - validated
createTestCert(x509.SHA384WithRSA, true), // Root - NOT validated
}
allowed := []signaturehash.Algorithm{
{Hash: hash.SHA256, Signature: signature.RSA}, // Only allows SHA256
}
// Should pass because root (SHA384) is not validated
err := validateCertificateSignatureAlgorithms(certs, allowed)
assert.NoError(t, err)
})

t.Run("Multi-cert chain with all allowed algorithms passes", func(t *testing.T) {
certs := []*x509.Certificate{
createTestCert(x509.SHA256WithRSA, false), // Leaf
createTestCert(x509.SHA384WithRSA, false), // Intermediate
createTestCert(x509.SHA512WithRSA, true), // Root (not validated)
}
allowed := []signaturehash.Algorithm{
{Hash: hash.SHA256, Signature: signature.RSA},
{Hash: hash.SHA384, Signature: signature.RSA},
// SHA512 not needed since root is not validated
}
err := validateCertificateSignatureAlgorithms(certs, allowed)
assert.NoError(t, err)
})

t.Run("Multi-cert chain with one disallowed intermediate fails", func(t *testing.T) {
certs := []*x509.Certificate{
createTestCert(x509.SHA256WithRSA, false), // Leaf - allowed
createTestCert(x509.SHA384WithRSA, false), // Intermediate - NOT allowed
createTestCert(x509.SHA512WithRSA, true), // Root
}
allowed := []signaturehash.Algorithm{
{Hash: hash.SHA256, Signature: signature.RSA}, // Only allows SHA256
}
err := validateCertificateSignatureAlgorithms(certs, allowed)
assert.ErrorIs(t, err, errInvalidCertificateSignatureAlgorithm)
})

t.Run("ECDSA certificates", func(t *testing.T) {
certs := []*x509.Certificate{
createTestCert(x509.ECDSAWithSHA256, false),
createTestCert(x509.ECDSAWithSHA384, false),
createTestCert(x509.ECDSAWithSHA512, true), // Root
}
allowed := []signaturehash.Algorithm{
{Hash: hash.SHA256, Signature: signature.ECDSA},
{Hash: hash.SHA384, Signature: signature.ECDSA},
}
err := validateCertificateSignatureAlgorithms(certs, allowed)
assert.NoError(t, err)
})

t.Run("RSA-PSS certificates", func(t *testing.T) {
certs := []*x509.Certificate{
createTestCert(x509.SHA256WithRSAPSS, false),
createTestCert(x509.SHA384WithRSAPSS, true), // Root
}
allowed := []signaturehash.Algorithm{
{Hash: hash.SHA256, Signature: signature.RSA},
}
err := validateCertificateSignatureAlgorithms(certs, allowed)
assert.NoError(t, err)
})

t.Run("Ed25519 certificates", func(t *testing.T) {
certs := []*x509.Certificate{
createTestCert(x509.PureEd25519, false),
createTestCert(x509.PureEd25519, true), // Root
}
allowed := []signaturehash.Algorithm{
{Hash: hash.None, Signature: signature.Ed25519},
}
err := validateCertificateSignatureAlgorithms(certs, allowed)
assert.NoError(t, err)
})

t.Run("Unsupported certificate algorithm", func(t *testing.T) {
certs := []*x509.Certificate{
createTestCert(x509.MD5WithRSA, false), // MD5 not supported
createTestCert(x509.SHA256WithRSA, true),
}
allowed := []signaturehash.Algorithm{
{Hash: hash.SHA256, Signature: signature.RSA},
}
err := validateCertificateSignatureAlgorithms(certs, allowed)
assert.Error(t, err)
// Should error from FromCertificate, not from algorithm mismatch
})

t.Run("Single cert chain does not validate", func(t *testing.T) {
// Single cert is treated as self-signed root, which is not validated
certs := []*x509.Certificate{
createTestCert(x509.SHA256WithRSA, true), // Root
}
allowed := []signaturehash.Algorithm{
{Hash: hash.SHA384, Signature: signature.ECDSA}, // Different algorithm
}
// Should pass because single root cert is not validated
err := validateCertificateSignatureAlgorithms(certs, allowed)
assert.NoError(t, err)
})
}
4 changes: 4 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ var (
//nolint:err113
errInvalidSignatureAlgorithm = &FatalError{Err: errors.New("invalid signature algorithm")}
//nolint:err113
errInvalidCertificateSignatureAlgorithm = &FatalError{
Err: errors.New("certificate uses a signature algorithm that is not allowed"),
}
//nolint:err113
errKeySignatureMismatch = &FatalError{Err: errors.New("expected and actual key signature do not match")}
//nolint:err113
errInvalidCertificateOID = &FatalError{Err: errors.New("certificate OID does not match signature algorithm")}
Expand Down
3 changes: 3 additions & 0 deletions flight0handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ func flight0Parse(
if cfg.connectionIDGenerator != nil {
state.remoteConnectionID = ext.CID
}
case *extension.SignatureAlgorithmsCert:
// Store the client's certificate signature schemes for later validation
state.remoteCertSignatureSchemes = ext.SignatureHashAlgorithms
}
}

Expand Down
6 changes: 6 additions & 0 deletions flight1handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ func flight1Generate(
},
}

if len(cfg.localCertSignatureSchemes) > 0 {
extensions = append(extensions, &extension.SignatureAlgorithmsCert{
SignatureHashAlgorithms: cfg.localCertSignatureSchemes,
})
}

var setEllipticCurveCryptographyClientHelloExtensions bool
for _, c := range cfg.localCipherSuites {
if c.ECC() {
Expand Down
6 changes: 6 additions & 0 deletions flight3handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,12 @@ func flight3Generate(
},
}

if len(cfg.localCertSignatureSchemes) > 0 {
extensions = append(extensions, &extension.SignatureAlgorithmsCert{
SignatureHashAlgorithms: cfg.localCertSignatureSchemes,
})
}

if state.namedCurve != 0 {
extensions = append(extensions, []extension.Extension{
&extension.SupportedEllipticCurves{
Expand Down
7 changes: 6 additions & 1 deletion flight4handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,12 @@ func flight4Parse(
var err error
var verified bool
if cfg.clientAuth >= VerifyClientCertIfGiven {
if chains, err = verifyClientCert(state.PeerCertificates, cfg.clientCAs); err != nil {
// Use cert-specific algorithms if present, otherwise fall back to signature_algorithms per RFC 8446
certAlgs := state.remoteCertSignatureSchemes
if len(certAlgs) == 0 {
certAlgs = cfg.localSignatureSchemes
}
if chains, err = verifyClientCert(state.PeerCertificates, cfg.clientCAs, certAlgs); err != nil {
return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, err
}
verified = true
Expand Down
6 changes: 5 additions & 1 deletion flight5handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,11 @@ func initializeCipherSuite(
}
var chains [][]*x509.Certificate
if !cfg.insecureSkipVerify {
if chains, err = verifyServerCert(state.PeerCertificates, cfg.rootCAs, cfg.serverName); err != nil {
certAlgs := cfg.localCertSignatureSchemes
if len(certAlgs) == 0 {
certAlgs = cfg.localSignatureSchemes
}
if chains, err = verifyServerCert(state.PeerCertificates, cfg.rootCAs, cfg.serverName, certAlgs); err != nil {
return &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, err
}
}
Expand Down
1 change: 1 addition & 0 deletions handshaker.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ type handshakeConfig struct {
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
Expand Down
Loading
Loading