Files
webrtc/quictransport.go
Sean DuBois f3d42e0120 Fix typo Quick -> Quic
Fix typo made when updating golangci-lint
2020-10-03 22:44:07 -07:00

174 lines
4.2 KiB
Go

// +build !js
// +build quic
package webrtc
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"errors"
"strings"
"sync"
"time"
"github.com/pion/dtls/v2/pkg/crypto/fingerprint"
"github.com/pion/logging"
"github.com/pion/quic"
"github.com/pion/webrtc/v3/internal/mux"
"github.com/pion/webrtc/v3/pkg/rtcerr"
)
// QUICTransport is a specialization of QuicTransportBase focused on
// peer-to-peer use cases and includes information relating to use of a
// QUIC transport with an ICE transport.
type QUICTransport struct {
lock sync.RWMutex
quic.TransportBase
iceTransport *ICETransport
certificates []Certificate
api *API
loggingFactory logging.LoggerFactory
}
var (
errQuicTransportFingerprintNoMatch = errors.New("no matching fingerprint")
errQuicTransportICEConnectionNotStarted = errors.New("ICE connection not started")
)
// NewQUICTransport creates a new QUICTransport.
// This constructor is part of the ORTC API. It is not
// meant to be used together with the basic WebRTC API.
// Note that the Quic transport is a draft and therefore
// highly experimental. It is currently not supported by
// any browsers yet.
func (api *API) NewQUICTransport(transport *ICETransport, certificates []Certificate) (*QUICTransport, error) {
t := &QUICTransport{
iceTransport: transport,
api: api,
loggingFactory: api.settingEngine.LoggerFactory,
}
if len(certificates) > 0 {
now := time.Now()
for _, x509Cert := range certificates {
if !x509Cert.Expires().IsZero() && now.After(x509Cert.Expires()) {
return nil, &rtcerr.InvalidAccessError{Err: ErrCertificateExpired}
}
t.certificates = append(t.certificates, x509Cert)
}
} else {
sk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, &rtcerr.UnknownError{Err: err}
}
certificate, err := GenerateCertificate(sk)
if err != nil {
return nil, err
}
t.certificates = []Certificate{*certificate}
}
return t, nil
}
// GetLocalParameters returns the Quic parameters of the local QUICParameters upon construction.
func (t *QUICTransport) GetLocalParameters() (QUICParameters, error) {
fingerprints := []DTLSFingerprint{}
for _, c := range t.certificates {
prints, err := c.GetFingerprints()
if err != nil {
return QUICParameters{}, err
}
fingerprints = append(fingerprints, prints...)
}
return QUICParameters{
Role: QUICRoleAuto, // always returns the default role
Fingerprints: fingerprints,
}, nil
}
// Start Quic transport with the parameters of the remote
func (t *QUICTransport) Start(remoteParameters QUICParameters) error {
t.lock.Lock()
defer t.lock.Unlock()
if err := t.ensureICEConn(); err != nil {
return err
}
cert := t.certificates[0]
isClient := true
switch remoteParameters.Role {
case QUICRoleClient:
isClient = true
case QUICRoleServer:
isClient = false
default:
if t.iceTransport.Role() == ICERoleControlling {
isClient = false
}
}
cfg := &quic.Config{
Client: isClient,
Certificate: cert.x509Cert,
PrivateKey: cert.privateKey,
LoggerFactory: t.loggingFactory,
}
endpoint := t.iceTransport.NewEndpoint(mux.MatchAll)
err := t.TransportBase.StartBase(endpoint, cfg)
if err != nil {
return err
}
// Check the fingerprint if a certificate was exchanged
remoteCerts := t.TransportBase.GetRemoteCertificates()
if len(remoteCerts) > 0 {
err := t.validateFingerPrint(remoteParameters, remoteCerts[0])
if err != nil {
return err
}
} else {
log := t.loggingFactory.NewLogger("quic-go")
log.Errorf("Warning: Certificate not checked")
}
return nil
}
func (t *QUICTransport) validateFingerPrint(remoteParameters QUICParameters, remoteCert *x509.Certificate) error {
for _, fp := range remoteParameters.Fingerprints {
hashAlgo, err := fingerprint.HashFromString(fp.Algorithm)
if err != nil {
return err
}
remoteValue, err := fingerprint.Fingerprint(remoteCert, hashAlgo)
if err != nil {
return err
}
if strings.EqualFold(remoteValue, fp.Value) {
return nil
}
}
return errQuicTransportFingerprintNoMatch
}
func (t *QUICTransport) ensureICEConn() error {
if t.iceTransport == nil ||
t.iceTransport.State() == ICETransportStateNew {
return errQuicTransportICEConnectionNotStarted
}
return nil
}