1// +build !js
2
3package webrtc
4
5import (
6	"crypto"
7	"crypto/ecdsa"
8	"crypto/rand"
9	"crypto/rsa"
10	"crypto/x509"
11	"crypto/x509/pkix"
12	"encoding/base64"
13	"encoding/hex"
14	"fmt"
15	"math/big"
16	"time"
17
18	"github.com/pion/dtls/v2/pkg/crypto/fingerprint"
19	"github.com/pion/webrtc/v3/pkg/rtcerr"
20)
21
22// Certificate represents a x509Cert used to authenticate WebRTC communications.
23type Certificate struct {
24	privateKey crypto.PrivateKey
25	x509Cert   *x509.Certificate
26	statsID    string
27}
28
29// NewCertificate generates a new x509 compliant Certificate to be used
30// by DTLS for encrypting data sent over the wire. This method differs from
31// GenerateCertificate by allowing to specify a template x509.Certificate to
32// be used in order to define certificate parameters.
33func NewCertificate(key crypto.PrivateKey, tpl x509.Certificate) (*Certificate, error) {
34	var err error
35	var certDER []byte
36	switch sk := key.(type) {
37	case *rsa.PrivateKey:
38		pk := sk.Public()
39		tpl.SignatureAlgorithm = x509.SHA256WithRSA
40		certDER, err = x509.CreateCertificate(rand.Reader, &tpl, &tpl, pk, sk)
41		if err != nil {
42			return nil, &rtcerr.UnknownError{Err: err}
43		}
44	case *ecdsa.PrivateKey:
45		pk := sk.Public()
46		tpl.SignatureAlgorithm = x509.ECDSAWithSHA256
47		certDER, err = x509.CreateCertificate(rand.Reader, &tpl, &tpl, pk, sk)
48		if err != nil {
49			return nil, &rtcerr.UnknownError{Err: err}
50		}
51	default:
52		return nil, &rtcerr.NotSupportedError{Err: ErrPrivateKeyType}
53	}
54
55	cert, err := x509.ParseCertificate(certDER)
56	if err != nil {
57		return nil, &rtcerr.UnknownError{Err: err}
58	}
59
60	return &Certificate{privateKey: key, x509Cert: cert, statsID: fmt.Sprintf("certificate-%d", time.Now().UnixNano())}, nil
61}
62
63// Equals determines if two certificates are identical by comparing both the
64// secretKeys and x509Certificates.
65func (c Certificate) Equals(o Certificate) bool {
66	switch cSK := c.privateKey.(type) {
67	case *rsa.PrivateKey:
68		if oSK, ok := o.privateKey.(*rsa.PrivateKey); ok {
69			if cSK.N.Cmp(oSK.N) != 0 {
70				return false
71			}
72			return c.x509Cert.Equal(o.x509Cert)
73		}
74		return false
75	case *ecdsa.PrivateKey:
76		if oSK, ok := o.privateKey.(*ecdsa.PrivateKey); ok {
77			if cSK.X.Cmp(oSK.X) != 0 || cSK.Y.Cmp(oSK.Y) != 0 {
78				return false
79			}
80			return c.x509Cert.Equal(o.x509Cert)
81		}
82		return false
83	default:
84		return false
85	}
86}
87
88// Expires returns the timestamp after which this certificate is no longer valid.
89func (c Certificate) Expires() time.Time {
90	if c.x509Cert == nil {
91		return time.Time{}
92	}
93	return c.x509Cert.NotAfter
94}
95
96// GetFingerprints returns the list of certificate fingerprints, one of which
97// is computed with the digest algorithm used in the certificate signature.
98func (c Certificate) GetFingerprints() ([]DTLSFingerprint, error) {
99	fingerprintAlgorithms := []crypto.Hash{crypto.SHA256}
100	res := make([]DTLSFingerprint, len(fingerprintAlgorithms))
101
102	i := 0
103	for _, algo := range fingerprintAlgorithms {
104		name, err := fingerprint.StringFromHash(algo)
105		if err != nil {
106			return nil, fmt.Errorf("%w: %v", ErrFailedToGenerateCertificateFingerprint, err)
107		}
108		value, err := fingerprint.Fingerprint(c.x509Cert, algo)
109		if err != nil {
110			return nil, fmt.Errorf("%w: %v", ErrFailedToGenerateCertificateFingerprint, err)
111		}
112		res[i] = DTLSFingerprint{
113			Algorithm: name,
114			Value:     value,
115		}
116	}
117
118	return res[:i+1], nil
119}
120
121// GenerateCertificate causes the creation of an X.509 certificate and
122// corresponding private key.
123func GenerateCertificate(secretKey crypto.PrivateKey) (*Certificate, error) {
124	origin := make([]byte, 16)
125	/* #nosec */
126	if _, err := rand.Read(origin); err != nil {
127		return nil, &rtcerr.UnknownError{Err: err}
128	}
129
130	// Max random value, a 130-bits integer, i.e 2^130 - 1
131	maxBigInt := new(big.Int)
132	/* #nosec */
133	maxBigInt.Exp(big.NewInt(2), big.NewInt(130), nil).Sub(maxBigInt, big.NewInt(1))
134	/* #nosec */
135	serialNumber, err := rand.Int(rand.Reader, maxBigInt)
136	if err != nil {
137		return nil, &rtcerr.UnknownError{Err: err}
138	}
139
140	return NewCertificate(secretKey, x509.Certificate{
141		ExtKeyUsage: []x509.ExtKeyUsage{
142			x509.ExtKeyUsageClientAuth,
143			x509.ExtKeyUsageServerAuth,
144		},
145		BasicConstraintsValid: true,
146		NotBefore:             time.Now(),
147		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
148		NotAfter:              time.Now().AddDate(0, 1, 0),
149		SerialNumber:          serialNumber,
150		Version:               2,
151		Subject:               pkix.Name{CommonName: hex.EncodeToString(origin)},
152		IsCA:                  true,
153	})
154}
155
156// CertificateFromX509 creates a new WebRTC Certificate from a given PrivateKey and Certificate
157//
158// This can be used if you want to share a certificate across multiple PeerConnections
159func CertificateFromX509(privateKey crypto.PrivateKey, certificate *x509.Certificate) Certificate {
160	return Certificate{privateKey, certificate, fmt.Sprintf("certificate-%d", time.Now().UnixNano())}
161}
162
163func (c Certificate) collectStats(report *statsReportCollector) error {
164	report.Collecting()
165
166	fingerPrintAlgo, err := c.GetFingerprints()
167	if err != nil {
168		return err
169	}
170
171	base64Certificate := base64.RawURLEncoding.EncodeToString(c.x509Cert.Raw)
172
173	stats := CertificateStats{
174		Timestamp:            statsTimestampFrom(time.Now()),
175		Type:                 StatsTypeCertificate,
176		ID:                   c.statsID,
177		Fingerprint:          fingerPrintAlgo[0].Value,
178		FingerprintAlgorithm: fingerPrintAlgo[0].Algorithm,
179		Base64Certificate:    base64Certificate,
180		IssuerCertificateID:  c.x509Cert.Issuer.String(),
181	}
182
183	report.Collect(stats.ID, stats)
184	return nil
185}
186