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